blob: 9efdfb5fc2af70c847c6d17347d82d4d1d4d98be [file] [log] [blame]
Monty Taylor16390e22012-11-04 22:20:36 +01001#! /usr/bin/env python
2# Copyright (C) 2011 OpenStack, LLC.
3# Copyright (c) 2012 Hewlett-Packard Development Company, L.P.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
17# manage_projects.py reads a project config file called projects.yaml
18# It should look like:
19
20# - homepage: http://openstack.org
21# gerrit-host: review.openstack.org
22# local-git-dir: /var/lib/git
23# gerrit-key: /home/gerrit2/review_site/etc/ssh_host_rsa_key
24# has-wiki: False
25# has-issues: False
26# has-downloads: False
27# ---
28# - project: PROJECT_NAME
29# options:
30# - has-wiki
31# - has-issues
32# - has-downloads
33# - has-pull-requests
34# homepage: Some homepage that isn't http://openstack.org
35# description: This is a great project
36# remote: https://gerrit.googlesource.com/gerrit
37# upstream: git://github.com/bushy/beards.git
38# acl_config: /path/to/gerrit/project.config
39
40
41import ConfigParser
42import logging
43import os
44import re
45import shlex
46import subprocess
47import tempfile
48import yaml
49
50import github
51import gerritlib.gerrit
52
53
54logging.basicConfig(level=logging.ERROR)
55log = logging.getLogger("manage_projects")
56
57
58def run_command(cmd, status=False, env={}):
59 cmd_list = shlex.split(str(cmd))
60 newenv = os.environ
61 newenv.update(env)
62 log.debug("Executing command: %s" % " ".join(cmd_list))
63 p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE,
64 stderr=subprocess.STDOUT, env=newenv)
65 (out, nothing) = p.communicate()
66 log.debug("Return code: %s" % p.returncode)
67 log.debug("Command said: %s" % out.strip())
68 if status:
69 return (p.returncode, out.strip())
70 return out.strip()
71
72
73def run_command_status(cmd, env={}):
74 return run_command(cmd, True, env)
75
76
77def git_command(repo_dir, sub_cmd, env={}):
78 git_dir = os.path.join(repo_dir, '.git')
79 cmd = "git --git-dir=%s --work-tree=%s %s" % (git_dir, repo_dir, sub_cmd)
80 status, _ = run_command(cmd, True, env)
81 return status
82
83
84def fetch_config(project, remote_url, repo_path, env={}):
85 status = git_command(repo_path, "fetch %s +refs/meta/config:"
86 "refs/remotes/gerrit-meta/config" % remote_url, env)
87 if status != 0:
88 print "Failed to fetch refs/meta/config for project: %s" % project
89 return False
90 # Because the following fails if executed more than once you should only
91 # run fetch_config once in each repo.
92 status = git_command(repo_path, "checkout -b config "
93 "remotes/gerrit-meta/config")
94 if status != 0:
95 print "Failed to checkout config for project: %s" % project
96 return False
97
98 return True
99
100
101def copy_acl_config(project, repo_path, acl_config):
102 if not os.path.exists(acl_config):
103 return False
104
105 acl_dest = os.path.join(repo_path, "project.config")
106 status, _ = run_command("cp %s %s" %
107 (acl_config, acl_dest), status=True)
108 if status == 0:
109 status = git_command(repo_path, "diff-index --quiet HEAD --")
110 if status != 0:
111 return True
112 return False
113
114
115def push_acl_config(project, remote_url, repo_path, env={}):
116 cmd = "commit -a -m'Update project config.' --author='Openstack Project " \
117 "Creator <openstack-infra@lists.openstack.org>'"
118 status = git_command(repo_path, cmd)
119 if status != 0:
120 print "Failed to commit config for project: %s" % project
121 return False
122 status = git_command(repo_path,
123 "push %s HEAD:refs/meta/config" %
124 remote_url, env)
125 if status != 0:
126 print "Failed to push config for project: %s" % project
127 return False
128 return True
129
130
131def _get_group_uuid(gerrit, group):
132 query = "select group_uuid from account_groups where name = '%s'" % group
133 data = gerrit.dbQuery(query)
134 if data:
135 for row in data:
136 if row["type"] == "row":
137 return row["columns"]["group_uuid"]
138 return None
139
140
141def get_group_uuid(gerrit, group):
142 uuid = _get_group_uuid(gerrit, group)
143 if uuid:
144 return uuid
145 gerrit.createGroup(group)
146 uuid = _get_group_uuid(gerrit, group)
147 if uuid:
148 return uuid
149 return None
150
151
152def create_groups_file(project, gerrit, repo_path):
153 acl_config = os.path.join(repo_path, "project.config")
154 group_file = os.path.join(repo_path, "groups")
155 uuids = {}
156 for line in open(acl_config, 'r'):
157 r = re.match(r'^\t.*group\s(.*)$', line)
158 if r:
159 group = r.group(1)
160 if group in uuids.keys():
161 continue
162 uuid = get_group_uuid(gerrit, group)
163 if uuid:
164 uuids[group] = uuid
165 else:
166 return False
167 if uuids:
168 with open(group_file, 'w') as fp:
169 for group, uuid in uuids.items():
170 fp.write("%s\t%s\n" % (uuid, group))
171 status = git_command(repo_path, "add groups")
172 if status != 0:
173 print "Failed to add groups file for project: %s" % project
174 return False
175 return True
176
177
178def make_ssh_wrapper(gerrit_user, gerrit_key):
179 (fd, name) = tempfile.mkstemp(text=True)
180 os.write(fd, '#!/bin/bash\n')
181 os.write(fd,
182 'ssh -i %s -l %s -o "StrictHostKeyChecking no" $@\n' %
183 (gerrit_key, gerrit_user))
184 os.close(fd)
185 os.chmod(name, 755)
186 return dict(GIT_SSH=name)
187
188
189PROJECTS_YAML = os.environ.get('PROJECTS_YAML',
190 '/home/gerrit2/projects.yaml')
191configs = [config for config in yaml.load_all(open(PROJECTS_YAML))]
192defaults = configs[0][0]
193default_has_issues = defaults.get('has-issues', False)
194default_has_downloads = defaults.get('has-downloads', False)
195default_has_wiki = defaults.get('has-wiki', False)
196
197LOCAL_GIT_DIR = defaults.get('local-git-dir', '/var/lib/git')
198GERRIT_HOST = defaults.get('gerrit-host')
199GERRIT_USER = defaults.get('gerrit-user')
200GERRIT_KEY = defaults.get('gerrit-key')
201GITHUB_SECURE_CONFIG = defaults.get('github-config',
202 '/etc/github/github-projects.secure.config')
203
204secure_config = ConfigParser.ConfigParser()
205secure_config.read(GITHUB_SECURE_CONFIG)
206
207# Project creation doesn't work via oauth
208ghub = github.Github(secure_config.get("github", "username"),
209 secure_config.get("github", "password"))
210orgs = ghub.get_user().get_orgs()
211orgs_dict = dict(zip([o.login.lower() for o in orgs], orgs))
212
213gerrit = gerritlib.gerrit.Gerrit('localhost',
214 GERRIT_USER,
215 29418,
216 GERRIT_KEY)
217project_list = gerrit.listProjects()
218ssh_env = make_ssh_wrapper(GERRIT_USER, GERRIT_KEY)
219try:
220
221 for section in configs[1]:
222 project = section['project']
223 options = section.get('options', dict())
224 description = section.get('description', None)
225 homepage = section.get('homepage', defaults.get('homepage', None))
226 upstream = section.get('upstream', None)
227
228 project_git = "%s.git" % project
229 project_dir = os.path.join(LOCAL_GIT_DIR, project_git)
230
231 # Find the project's repo
232 project_split = project.split('/', 1)
233 if len(project_split) > 1:
234 repo_name = project_split[1]
235 else:
236 repo_name = project
237 has_issues = 'has-issues' in options or default_has_issues
238 has_downloads = 'has-downloads' in options or default_has_downloads
239 has_wiki = 'has-wiki' in options or default_has_wiki
240 org = orgs_dict[project_split[0].lower()]
241 try:
242 repo = org.get_repo(repo_name)
243 except github.GithubException:
244 repo = org.create_repo(repo_name,
245 homepage=homepage,
246 has_issues=has_issues,
247 has_downloads=has_downloads,
248 has_wiki=has_wiki)
249 if description:
250 repo.edit(repo_name, description=description)
251 if homepage:
252 repo.edit(repo_name, homepage=homepage)
253
254 repo.edit(repo_name, has_issues=has_issues,
255 has_downloads=has_downloads,
256 has_wiki=has_wiki)
257
258 if 'gerrit' not in [team.name for team in repo.get_teams()]:
259 teams = org.get_teams()
260 teams_dict = dict(zip([t.name.lower() for t in teams], teams))
261 teams_dict['gerrit'].add_to_repos(repo)
262
263 remote_url = "ssh://localhost:29418/%s" % project
264 if project not in project_list:
265 tmpdir = tempfile.mkdtemp()
266 try:
267 repo_path = os.path.join(tmpdir, 'repo')
268 if upstream:
269 run_command("git clone %(upstream)s %(repo_path)" %
270 dict(upstream=upstream, repo_path=repo_path))
271 else:
272 run_command("git init %s" % repo_path)
273 with open(os.path.join(repo_path,
274 ".gitreview"), 'w') as gitreview:
275 gitreview.write("""
276[gerrit]
277host=%s
278port=29418
279project=%s
280""" % (GERRIT_HOST, project_git))
281 git_command(repo_path, "add .gitreview")
Clark Boylan33c46d42012-11-14 08:53:37 -0800282 cmd = "commit -a -m'Added .gitreview' --author=" \
283 "'Openstack Project Creator " \
284 "<openstack-infra@lists.openstack.org>'"
285 git_command(repo_path, cmd)
Monty Taylor16390e22012-11-04 22:20:36 +0100286 gerrit.createProject(project)
287
288 if not os.path.exists(project_dir):
289 run_command("git --bare init %s" % project_dir)
290 run_command("chown -R gerrit2:gerrit2 %s" % project_dir)
291
292 git_command(repo_path,
293 "push %s HEAD:refs/heads/master" % remote_url,
294 env=ssh_env)
295 git_command(repo_path,
296 "push --tags %s" % remote_url, env=ssh_env)
297 finally:
298 run_command("rm -fr %s" % tmpdir)
299
300 if 'acl_config' in section:
301 tmpdir = tempfile.mkdtemp()
302 try:
303 repo_path = os.path.join(tmpdir, 'repo')
304 ret, _ = run_command_status("git init %s" % repo_path)
305 if ret != 0:
306 continue
307 if (fetch_config(project, remote_url, repo_path, ssh_env) and
308 copy_acl_config(project, repo_path,
309 section['acl_config']) and
310 create_groups_file(project, gerrit, repo_path)):
311 push_acl_config(project, remote_url, repo_path, ssh_env)
312 finally:
313 run_command("rm -fr %s" % tmpdir)
314finally:
315 os.unlink(ssh_env['GIT_SSH'])