blob: 73067af2cb964d04684169e7efc8e4785ef7d73e [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
Clark Boylanda402e52012-12-04 15:23:43 -080053import jeepyb.gerritdb
54
Monty Taylor16390e22012-11-04 22:20:36 +010055logging.basicConfig(level=logging.ERROR)
56log = logging.getLogger("manage_projects")
57
58
59def run_command(cmd, status=False, env={}):
60 cmd_list = shlex.split(str(cmd))
61 newenv = os.environ
62 newenv.update(env)
63 log.debug("Executing command: %s" % " ".join(cmd_list))
64 p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE,
65 stderr=subprocess.STDOUT, env=newenv)
66 (out, nothing) = p.communicate()
67 log.debug("Return code: %s" % p.returncode)
68 log.debug("Command said: %s" % out.strip())
69 if status:
70 return (p.returncode, out.strip())
71 return out.strip()
72
73
74def run_command_status(cmd, env={}):
75 return run_command(cmd, True, env)
76
77
78def git_command(repo_dir, sub_cmd, env={}):
79 git_dir = os.path.join(repo_dir, '.git')
80 cmd = "git --git-dir=%s --work-tree=%s %s" % (git_dir, repo_dir, sub_cmd)
81 status, _ = run_command(cmd, True, env)
82 return status
83
84
Clark Boylan808b5132012-11-14 15:52:02 -080085def git_command_output(repo_dir, sub_cmd, env={}):
86 git_dir = os.path.join(repo_dir, '.git')
87 cmd = "git --git-dir=%s --work-tree=%s %s" % (git_dir, repo_dir, sub_cmd)
88 status, out = run_command(cmd, True, env)
89 return (status, out)
90
91
Monty Taylor16390e22012-11-04 22:20:36 +010092def fetch_config(project, remote_url, repo_path, env={}):
93 status = git_command(repo_path, "fetch %s +refs/meta/config:"
94 "refs/remotes/gerrit-meta/config" % remote_url, env)
95 if status != 0:
96 print "Failed to fetch refs/meta/config for project: %s" % project
97 return False
98 # Because the following fails if executed more than once you should only
99 # run fetch_config once in each repo.
100 status = git_command(repo_path, "checkout -b config "
101 "remotes/gerrit-meta/config")
102 if status != 0:
103 print "Failed to checkout config for project: %s" % project
104 return False
105
106 return True
107
108
109def copy_acl_config(project, repo_path, acl_config):
110 if not os.path.exists(acl_config):
111 return False
112
113 acl_dest = os.path.join(repo_path, "project.config")
114 status, _ = run_command("cp %s %s" %
115 (acl_config, acl_dest), status=True)
116 if status == 0:
Monty Taylorda3bada2012-11-22 09:38:22 -0800117 status = git_command(repo_path, "diff-index --quiet HEAD --")
Monty Taylor16390e22012-11-04 22:20:36 +0100118 if status != 0:
119 return True
120 return False
121
122
123def push_acl_config(project, remote_url, repo_path, env={}):
124 cmd = "commit -a -m'Update project config.' --author='Openstack Project " \
Monty Taylorda3bada2012-11-22 09:38:22 -0800125 "Creator <openstack-infra@lists.openstack.org>'"
Monty Taylor16390e22012-11-04 22:20:36 +0100126 status = git_command(repo_path, cmd)
127 if status != 0:
128 print "Failed to commit config for project: %s" % project
129 return False
Clark Boylan808b5132012-11-14 15:52:02 -0800130 status, out = git_command_output(repo_path,
Monty Taylorda3bada2012-11-22 09:38:22 -0800131 "push %s HEAD:refs/meta/config" %
132 remote_url, env)
Monty Taylor16390e22012-11-04 22:20:36 +0100133 if status != 0:
134 print "Failed to push config for project: %s" % project
Clark Boylan808b5132012-11-14 15:52:02 -0800135 print out
Monty Taylor16390e22012-11-04 22:20:36 +0100136 return False
137 return True
138
139
140def _get_group_uuid(gerrit, group):
Clark Boylanda402e52012-12-04 15:23:43 -0800141 cursor = jeepyb.gerritdb.connect().cursor()
142 query = "SELECT group_uuid FROM account_groups WHERE name = %s"
143 cursor.execute(query, group)
144 data = cursor.fetchone()
Monty Taylor16390e22012-11-04 22:20:36 +0100145 if data:
Clark Boylanda402e52012-12-04 15:23:43 -0800146 return data[0]
Monty Taylor16390e22012-11-04 22:20:36 +0100147 return None
148
149
150def get_group_uuid(gerrit, group):
151 uuid = _get_group_uuid(gerrit, group)
152 if uuid:
153 return uuid
Clark Boylan0f2a4402012-12-02 10:11:57 -0800154 gerrit.createGroup(group, owner="Administrators")
Monty Taylor16390e22012-11-04 22:20:36 +0100155 uuid = _get_group_uuid(gerrit, group)
156 if uuid:
157 return uuid
158 return None
159
160
161def create_groups_file(project, gerrit, repo_path):
162 acl_config = os.path.join(repo_path, "project.config")
163 group_file = os.path.join(repo_path, "groups")
164 uuids = {}
165 for line in open(acl_config, 'r'):
Clark Boylan5c9670f2012-11-14 17:48:55 -0800166 r = re.match(r'^\s+.*group\s+(.*)$', line)
Monty Taylor16390e22012-11-04 22:20:36 +0100167 if r:
168 group = r.group(1)
169 if group in uuids.keys():
170 continue
171 uuid = get_group_uuid(gerrit, group)
172 if uuid:
173 uuids[group] = uuid
174 else:
175 return False
176 if uuids:
177 with open(group_file, 'w') as fp:
178 for group, uuid in uuids.items():
179 fp.write("%s\t%s\n" % (uuid, group))
180 status = git_command(repo_path, "add groups")
181 if status != 0:
182 print "Failed to add groups file for project: %s" % project
183 return False
184 return True
185
186
187def make_ssh_wrapper(gerrit_user, gerrit_key):
188 (fd, name) = tempfile.mkstemp(text=True)
189 os.write(fd, '#!/bin/bash\n')
190 os.write(fd,
191 'ssh -i %s -l %s -o "StrictHostKeyChecking no" $@\n' %
192 (gerrit_key, gerrit_user))
193 os.close(fd)
James E. Blaird7b54872012-11-30 16:22:57 -0800194 os.chmod(name, 0755)
Monty Taylor16390e22012-11-04 22:20:36 +0100195 return dict(GIT_SSH=name)
196
197
Monty Taylorda3bada2012-11-22 09:38:22 -0800198def main():
Monty Taylor16390e22012-11-04 22:20:36 +0100199
Monty Taylorda3bada2012-11-22 09:38:22 -0800200 PROJECTS_YAML = os.environ.get('PROJECTS_YAML',
201 '/home/gerrit2/projects.yaml')
202 configs = [config for config in yaml.load_all(open(PROJECTS_YAML))]
203 defaults = configs[0][0]
204 default_has_issues = defaults.get('has-issues', False)
205 default_has_downloads = defaults.get('has-downloads', False)
206 default_has_wiki = defaults.get('has-wiki', False)
Monty Taylor16390e22012-11-04 22:20:36 +0100207
Monty Taylorda3bada2012-11-22 09:38:22 -0800208 LOCAL_GIT_DIR = defaults.get('local-git-dir', '/var/lib/git')
209 GERRIT_HOST = defaults.get('gerrit-host')
210 GERRIT_USER = defaults.get('gerrit-user')
211 GERRIT_KEY = defaults.get('gerrit-key')
Monty Taylor16390e22012-11-04 22:20:36 +0100212
Monty Taylorda3bada2012-11-22 09:38:22 -0800213 GITHUB_SECURE_CONFIG = defaults.get(
214 'github-config',
215 '/etc/github/github-projects.secure.config')
Monty Taylor16390e22012-11-04 22:20:36 +0100216
Monty Taylorda3bada2012-11-22 09:38:22 -0800217 secure_config = ConfigParser.ConfigParser()
218 secure_config.read(GITHUB_SECURE_CONFIG)
Monty Taylor16390e22012-11-04 22:20:36 +0100219
Monty Taylorda3bada2012-11-22 09:38:22 -0800220 # Project creation doesn't work via oauth
221 ghub = github.Github(secure_config.get("github", "username"),
222 secure_config.get("github", "password"))
223 orgs = ghub.get_user().get_orgs()
224 orgs_dict = dict(zip([o.login.lower() for o in orgs], orgs))
Monty Taylor16390e22012-11-04 22:20:36 +0100225
Monty Taylorda3bada2012-11-22 09:38:22 -0800226 gerrit = gerritlib.gerrit.Gerrit('localhost',
227 GERRIT_USER,
228 29418,
229 GERRIT_KEY)
230 project_list = gerrit.listProjects()
231 ssh_env = make_ssh_wrapper(GERRIT_USER, GERRIT_KEY)
232 try:
Monty Taylor16390e22012-11-04 22:20:36 +0100233
Monty Taylorda3bada2012-11-22 09:38:22 -0800234 for section in configs[1]:
235 project = section['project']
236 options = section.get('options', dict())
237 description = section.get('description', None)
238 homepage = section.get('homepage', defaults.get('homepage', None))
239 upstream = section.get('upstream', None)
Monty Taylor16390e22012-11-04 22:20:36 +0100240
Monty Taylorda3bada2012-11-22 09:38:22 -0800241 project_git = "%s.git" % project
242 project_dir = os.path.join(LOCAL_GIT_DIR, project_git)
Monty Taylor16390e22012-11-04 22:20:36 +0100243
Monty Taylorda3bada2012-11-22 09:38:22 -0800244 # Find the project's repo
245 project_split = project.split('/', 1)
246 if len(project_split) > 1:
247 repo_name = project_split[1]
248 else:
249 repo_name = project
250 has_issues = 'has-issues' in options or default_has_issues
251 has_downloads = 'has-downloads' in options or default_has_downloads
252 has_wiki = 'has-wiki' in options or default_has_wiki
Monty Taylor16390e22012-11-04 22:20:36 +0100253 try:
Monty Taylorda3bada2012-11-22 09:38:22 -0800254 org = orgs_dict[project_split[0].lower()]
255 except KeyError:
256 # We do not have control of this github org ignore the project.
257 continue
Monty Taylor16390e22012-11-04 22:20:36 +0100258 try:
Monty Taylorda3bada2012-11-22 09:38:22 -0800259 repo = org.get_repo(repo_name)
260 except github.GithubException:
261 repo = org.create_repo(repo_name,
262 homepage=homepage,
263 has_issues=has_issues,
264 has_downloads=has_downloads,
265 has_wiki=has_wiki)
266 if description:
267 repo.edit(repo_name, description=description)
268 if homepage:
269 repo.edit(repo_name, homepage=homepage)
270
271 repo.edit(repo_name, has_issues=has_issues,
272 has_downloads=has_downloads,
273 has_wiki=has_wiki)
274
275 if 'gerrit' not in [team.name for team in repo.get_teams()]:
276 teams = org.get_teams()
277 teams_dict = dict(zip([t.name.lower() for t in teams], teams))
278 teams_dict['gerrit'].add_to_repos(repo)
279
280 remote_url = "ssh://localhost:29418/%s" % project
281 if project not in project_list:
282 tmpdir = tempfile.mkdtemp()
283 try:
284 repo_path = os.path.join(tmpdir, 'repo')
285 if upstream:
286 run_command("git clone %(upstream)s %(repo_path)s" %
287 dict(upstream=upstream,
288 repo_path=repo_path))
Clark Boylane0c725b2012-11-30 15:15:55 -0800289 git_command(repo_path,
290 "fetch origin "
291 "+refs/heads/*:refs/copy/heads/*",
292 env=ssh_env)
293 push_string = "push %s +refs/copy/heads/*:refs/heads/*"
Monty Taylorda3bada2012-11-22 09:38:22 -0800294 else:
295 run_command("git init %s" % repo_path)
296 with open(os.path.join(repo_path,
297 ".gitreview"),
298 'w') as gitreview:
299 gitreview.write("""
300 [gerrit]
301 host=%s
302 port=29418
303 project=%s
304 """ % (GERRIT_HOST, project_git))
305 git_command(repo_path, "add .gitreview")
306 cmd = "commit -a -m'Added .gitreview' --author=" \
307 "'Openstack Project Creator " \
308 "<openstack-infra@lists.openstack.org>'"
309 git_command(repo_path, cmd)
Clark Boylane0c725b2012-11-30 15:15:55 -0800310 push_string = "push --all %s"
Monty Taylorda3bada2012-11-22 09:38:22 -0800311 gerrit.createProject(project)
312
313 if not os.path.exists(project_dir):
314 run_command("git --bare init %s" % project_dir)
315 run_command("chown -R gerrit2:gerrit2 %s"
316 % project_dir)
317
318 git_command(repo_path,
Clark Boylane0c725b2012-11-30 15:15:55 -0800319 push_string % remote_url,
Monty Taylorda3bada2012-11-22 09:38:22 -0800320 env=ssh_env)
321 git_command(repo_path,
322 "push --tags %s" % remote_url, env=ssh_env)
323 finally:
324 run_command("rm -fr %s" % tmpdir)
325
326 if 'acl_config' in section:
327 tmpdir = tempfile.mkdtemp()
328 try:
329 repo_path = os.path.join(tmpdir, 'repo')
330 ret, _ = run_command_status("git init %s" % repo_path)
331 if ret != 0:
332 continue
333 if (fetch_config(project,
334 remote_url,
335 repo_path,
336 ssh_env) and
337 copy_acl_config(project, repo_path,
338 section['acl_config']) and
339 create_groups_file(project, gerrit, repo_path)):
340 push_acl_config(project,
341 remote_url,
342 repo_path,
343 ssh_env)
344 finally:
345 run_command("rm -fr %s" % tmpdir)
346 finally:
347 os.unlink(ssh_env['GIT_SSH'])