blob: 2137ea0a381022f4a19b31dd2afb2929e6509f94 [file] [log] [blame]
Monty Taylor16390e22012-11-04 22:20:36 +01001#! /usr/bin/env python
2# Copyright (C) 2011 OpenStack, LLC.
James E. Blair18e82562013-04-04 16:20:32 +00003# Copyright (c) 2012 Hewlett-Packard Development Company, L.P.
Monty Taylor16390e22012-11-04 22:20:36 +01004#
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
James E. Blair18e82562013-04-04 16:20:32 +000017# 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# acl-dir: /home/gerrit2/acls
28# acl-base: /home/gerrit2/acls/project.config
29# ---
30# - project: PROJECT_NAME
31# options:
32# - has-wiki
33# - has-issues
34# - has-downloads
35# - has-pull-requests
36# homepage: Some homepage that isn't http://openstack.org
37# description: This is a great project
38# remote: https://gerrit.googlesource.com/gerrit
39# upstream: git://github.com/bushy/beards.git
40# acl-config: /path/to/gerrit/project.config
41# acl-append:
42# - /path/to/gerrit/project.config
43# acl-parameters:
44# project: OTHER_PROJECT_NAME
45
46
Monty Taylor16390e22012-11-04 22:20:36 +010047import ConfigParser
48import logging
49import os
50import re
51import shlex
52import subprocess
53import tempfile
James E. Blair18e82562013-04-04 16:20:32 +000054import yaml
Monty Taylor16390e22012-11-04 22:20:36 +010055
56import github
57import gerritlib.gerrit
58
Clark Boylanda402e52012-12-04 15:23:43 -080059import jeepyb.gerritdb
60
Monty Taylor16390e22012-11-04 22:20:36 +010061logging.basicConfig(level=logging.ERROR)
62log = logging.getLogger("manage_projects")
63
64
65def run_command(cmd, status=False, env={}):
66 cmd_list = shlex.split(str(cmd))
67 newenv = os.environ
68 newenv.update(env)
69 log.debug("Executing command: %s" % " ".join(cmd_list))
70 p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE,
71 stderr=subprocess.STDOUT, env=newenv)
72 (out, nothing) = p.communicate()
73 log.debug("Return code: %s" % p.returncode)
74 log.debug("Command said: %s" % out.strip())
75 if status:
76 return (p.returncode, out.strip())
77 return out.strip()
78
79
80def run_command_status(cmd, env={}):
81 return run_command(cmd, True, env)
82
83
84def git_command(repo_dir, sub_cmd, env={}):
85 git_dir = os.path.join(repo_dir, '.git')
86 cmd = "git --git-dir=%s --work-tree=%s %s" % (git_dir, repo_dir, sub_cmd)
87 status, _ = run_command(cmd, True, env)
88 return status
89
90
Clark Boylan808b5132012-11-14 15:52:02 -080091def git_command_output(repo_dir, sub_cmd, env={}):
92 git_dir = os.path.join(repo_dir, '.git')
93 cmd = "git --git-dir=%s --work-tree=%s %s" % (git_dir, repo_dir, sub_cmd)
94 status, out = run_command(cmd, True, env)
95 return (status, out)
96
97
Monty Taylor863a6712013-03-03 09:52:36 -050098def write_acl_config(project, acl_dir, acl_base, acl_append, parameters):
99 project_parts = os.path.split(project)
100 if len(project_parts) > 1:
101 repo_base = os.path.join(acl_dir, *project_parts[:-1])
102 if not os.path.exists(repo_base):
James E. Blair22e1c692013-03-14 11:40:37 -0700103 os.makedirs(repo_base)
Monty Taylor863a6712013-03-03 09:52:36 -0500104 if not os.path.isdir(repo_base):
105 return 1
Monty Taylore8bb6e42013-03-08 15:01:56 -0500106 project = project_parts[-1]
107 config_file = os.path.join(repo_base, "%s.config" % project)
Monty Taylor863a6712013-03-03 09:52:36 -0500108 else:
109 config_file = os.path.join(acl_dir, "%s.config" % project)
James E. Blair18e82562013-04-04 16:20:32 +0000110 if 'project' not in parameters:
111 parameters['project'] = project
Monty Taylor863a6712013-03-03 09:52:36 -0500112 with open(config_file, 'w') as config:
113 if acl_base and os.path.exists(acl_base):
114 config.write(open(acl_base, 'r').read())
115 for acl_snippet in acl_append:
116 if not os.path.exists(acl_snippet):
117 acl_snippet = os.path.join(acl_dir, acl_snippet)
118 if not os.path.exists(acl_snippet):
119 continue
120 with open(acl_snippet, 'r') as append_content:
121 config.write(append_content.read() % parameters)
122
123
Monty Taylor16390e22012-11-04 22:20:36 +0100124def fetch_config(project, remote_url, repo_path, env={}):
125 status = git_command(repo_path, "fetch %s +refs/meta/config:"
126 "refs/remotes/gerrit-meta/config" % remote_url, env)
127 if status != 0:
128 print "Failed to fetch refs/meta/config for project: %s" % project
129 return False
130 # Because the following fails if executed more than once you should only
131 # run fetch_config once in each repo.
132 status = git_command(repo_path, "checkout -b config "
133 "remotes/gerrit-meta/config")
134 if status != 0:
135 print "Failed to checkout config for project: %s" % project
136 return False
137
138 return True
139
140
141def copy_acl_config(project, repo_path, acl_config):
142 if not os.path.exists(acl_config):
143 return False
144
145 acl_dest = os.path.join(repo_path, "project.config")
146 status, _ = run_command("cp %s %s" %
147 (acl_config, acl_dest), status=True)
148 if status == 0:
Monty Taylorda3bada2012-11-22 09:38:22 -0800149 status = git_command(repo_path, "diff-index --quiet HEAD --")
Monty Taylor16390e22012-11-04 22:20:36 +0100150 if status != 0:
151 return True
152 return False
153
154
155def push_acl_config(project, remote_url, repo_path, env={}):
156 cmd = "commit -a -m'Update project config.' --author='Openstack Project " \
Monty Taylorda3bada2012-11-22 09:38:22 -0800157 "Creator <openstack-infra@lists.openstack.org>'"
Monty Taylor16390e22012-11-04 22:20:36 +0100158 status = git_command(repo_path, cmd)
159 if status != 0:
160 print "Failed to commit config for project: %s" % project
161 return False
Clark Boylan808b5132012-11-14 15:52:02 -0800162 status, out = git_command_output(repo_path,
Monty Taylorda3bada2012-11-22 09:38:22 -0800163 "push %s HEAD:refs/meta/config" %
164 remote_url, env)
Monty Taylor16390e22012-11-04 22:20:36 +0100165 if status != 0:
166 print "Failed to push config for project: %s" % project
Clark Boylan808b5132012-11-14 15:52:02 -0800167 print out
Monty Taylor16390e22012-11-04 22:20:36 +0100168 return False
169 return True
170
171
172def _get_group_uuid(gerrit, group):
Clark Boylanda402e52012-12-04 15:23:43 -0800173 cursor = jeepyb.gerritdb.connect().cursor()
174 query = "SELECT group_uuid FROM account_groups WHERE name = %s"
175 cursor.execute(query, group)
176 data = cursor.fetchone()
Monty Taylor16390e22012-11-04 22:20:36 +0100177 if data:
Clark Boylanda402e52012-12-04 15:23:43 -0800178 return data[0]
Monty Taylor16390e22012-11-04 22:20:36 +0100179 return None
180
181
182def get_group_uuid(gerrit, group):
183 uuid = _get_group_uuid(gerrit, group)
184 if uuid:
185 return uuid
Clark Boylan0f2a4402012-12-02 10:11:57 -0800186 gerrit.createGroup(group, owner="Administrators")
Monty Taylor16390e22012-11-04 22:20:36 +0100187 uuid = _get_group_uuid(gerrit, group)
188 if uuid:
189 return uuid
190 return None
191
192
193def create_groups_file(project, gerrit, repo_path):
194 acl_config = os.path.join(repo_path, "project.config")
195 group_file = os.path.join(repo_path, "groups")
196 uuids = {}
197 for line in open(acl_config, 'r'):
Clark Boylan5c9670f2012-11-14 17:48:55 -0800198 r = re.match(r'^\s+.*group\s+(.*)$', line)
Monty Taylor16390e22012-11-04 22:20:36 +0100199 if r:
200 group = r.group(1)
201 if group in uuids.keys():
202 continue
203 uuid = get_group_uuid(gerrit, group)
204 if uuid:
205 uuids[group] = uuid
206 else:
207 return False
208 if uuids:
209 with open(group_file, 'w') as fp:
210 for group, uuid in uuids.items():
211 fp.write("%s\t%s\n" % (uuid, group))
212 status = git_command(repo_path, "add groups")
213 if status != 0:
214 print "Failed to add groups file for project: %s" % project
215 return False
216 return True
217
218
219def make_ssh_wrapper(gerrit_user, gerrit_key):
220 (fd, name) = tempfile.mkstemp(text=True)
221 os.write(fd, '#!/bin/bash\n')
222 os.write(fd,
223 'ssh -i %s -l %s -o "StrictHostKeyChecking no" $@\n' %
224 (gerrit_key, gerrit_user))
225 os.close(fd)
James E. Blaird7b54872012-11-30 16:22:57 -0800226 os.chmod(name, 0755)
Monty Taylor16390e22012-11-04 22:20:36 +0100227 return dict(GIT_SSH=name)
228
229
Monty Taylorda3bada2012-11-22 09:38:22 -0800230def main():
James E. Blair18e82562013-04-04 16:20:32 +0000231
Monty Taylorda3bada2012-11-22 09:38:22 -0800232 PROJECTS_YAML = os.environ.get('PROJECTS_YAML',
233 '/home/gerrit2/projects.yaml')
James E. Blair18e82562013-04-04 16:20:32 +0000234 configs = [config for config in yaml.load_all(open(PROJECTS_YAML))]
235 defaults = configs[0][0]
236 default_has_issues = defaults.get('has-issues', False)
237 default_has_downloads = defaults.get('has-downloads', False)
238 default_has_wiki = defaults.get('has-wiki', False)
Monty Taylor16390e22012-11-04 22:20:36 +0100239
James E. Blair18e82562013-04-04 16:20:32 +0000240 LOCAL_GIT_DIR = defaults.get('local-git-dir', '/var/lib/git')
241 ACL_DIR = defaults.get('acl-dir')
242 GERRIT_HOST = defaults.get('gerrit-host')
243 GERRIT_USER = defaults.get('gerrit-user')
244 GERRIT_KEY = defaults.get('gerrit-key')
Monty Taylor16390e22012-11-04 22:20:36 +0100245
James E. Blair18e82562013-04-04 16:20:32 +0000246 GITHUB_SECURE_CONFIG = defaults.get(
247 'github-config',
248 '/etc/github/github-projects.secure.config')
249
Monty Taylorda3bada2012-11-22 09:38:22 -0800250 secure_config = ConfigParser.ConfigParser()
251 secure_config.read(GITHUB_SECURE_CONFIG)
Monty Taylor16390e22012-11-04 22:20:36 +0100252
Monty Taylorda3bada2012-11-22 09:38:22 -0800253 # Project creation doesn't work via oauth
254 ghub = github.Github(secure_config.get("github", "username"),
255 secure_config.get("github", "password"))
256 orgs = ghub.get_user().get_orgs()
257 orgs_dict = dict(zip([o.login.lower() for o in orgs], orgs))
Monty Taylor16390e22012-11-04 22:20:36 +0100258
Monty Taylorda3bada2012-11-22 09:38:22 -0800259 gerrit = gerritlib.gerrit.Gerrit('localhost',
260 GERRIT_USER,
261 29418,
262 GERRIT_KEY)
263 project_list = gerrit.listProjects()
264 ssh_env = make_ssh_wrapper(GERRIT_USER, GERRIT_KEY)
265 try:
Monty Taylor16390e22012-11-04 22:20:36 +0100266
James E. Blair18e82562013-04-04 16:20:32 +0000267 for section in configs[1]:
268 project = section['project']
269 options = section.get('options', dict())
270 description = section.get('description', None)
271 homepage = section.get('homepage', defaults.get('homepage', None))
272 upstream = section.get('upstream', None)
273
Monty Taylorda3bada2012-11-22 09:38:22 -0800274 project_git = "%s.git" % project
275 project_dir = os.path.join(LOCAL_GIT_DIR, project_git)
Monty Taylor16390e22012-11-04 22:20:36 +0100276
Monty Taylorda3bada2012-11-22 09:38:22 -0800277 # Find the project's repo
278 project_split = project.split('/', 1)
279 if len(project_split) > 1:
280 repo_name = project_split[1]
281 else:
282 repo_name = project
James E. Blair18e82562013-04-04 16:20:32 +0000283 has_issues = 'has-issues' in options or default_has_issues
284 has_downloads = 'has-downloads' in options or default_has_downloads
285 has_wiki = 'has-wiki' in options or default_has_wiki
Monty Taylor16390e22012-11-04 22:20:36 +0100286 try:
Monty Taylorda3bada2012-11-22 09:38:22 -0800287 org = orgs_dict[project_split[0].lower()]
288 except KeyError:
289 # We do not have control of this github org ignore the project.
290 continue
Monty Taylor16390e22012-11-04 22:20:36 +0100291 try:
Monty Taylorda3bada2012-11-22 09:38:22 -0800292 repo = org.get_repo(repo_name)
293 except github.GithubException:
James E. Blair18e82562013-04-04 16:20:32 +0000294 repo = org.create_repo(repo_name,
295 homepage=homepage,
296 has_issues=has_issues,
297 has_downloads=has_downloads,
298 has_wiki=has_wiki)
299 if description:
300 repo.edit(repo_name, description=description)
301 if homepage:
302 repo.edit(repo_name, homepage=homepage)
Monty Taylorda3bada2012-11-22 09:38:22 -0800303
James E. Blair18e82562013-04-04 16:20:32 +0000304 repo.edit(repo_name, has_issues=has_issues,
305 has_downloads=has_downloads,
306 has_wiki=has_wiki)
Monty Taylorda3bada2012-11-22 09:38:22 -0800307
308 if 'gerrit' not in [team.name for team in repo.get_teams()]:
309 teams = org.get_teams()
310 teams_dict = dict(zip([t.name.lower() for t in teams], teams))
311 teams_dict['gerrit'].add_to_repos(repo)
312
313 remote_url = "ssh://localhost:29418/%s" % project
314 if project not in project_list:
315 tmpdir = tempfile.mkdtemp()
316 try:
317 repo_path = os.path.join(tmpdir, 'repo')
James E. Blair18e82562013-04-04 16:20:32 +0000318 if upstream:
Monty Taylorda3bada2012-11-22 09:38:22 -0800319 run_command("git clone %(upstream)s %(repo_path)s" %
James E. Blair18e82562013-04-04 16:20:32 +0000320 dict(upstream=upstream,
Monty Taylorda3bada2012-11-22 09:38:22 -0800321 repo_path=repo_path))
Clark Boylane0c725b2012-11-30 15:15:55 -0800322 git_command(repo_path,
323 "fetch origin "
324 "+refs/heads/*:refs/copy/heads/*",
325 env=ssh_env)
326 push_string = "push %s +refs/copy/heads/*:refs/heads/*"
Monty Taylorda3bada2012-11-22 09:38:22 -0800327 else:
328 run_command("git init %s" % repo_path)
329 with open(os.path.join(repo_path,
330 ".gitreview"),
331 'w') as gitreview:
Jeremy Stanleya4757602013-01-03 13:46:13 +0000332 gitreview.write("""[gerrit]
333host=%s
334port=29418
335project=%s
336""" % (GERRIT_HOST, project_git))
Monty Taylorda3bada2012-11-22 09:38:22 -0800337 git_command(repo_path, "add .gitreview")
338 cmd = "commit -a -m'Added .gitreview' --author=" \
339 "'Openstack Project Creator " \
340 "<openstack-infra@lists.openstack.org>'"
341 git_command(repo_path, cmd)
Clark Boylane0c725b2012-11-30 15:15:55 -0800342 push_string = "push --all %s"
Monty Taylorda3bada2012-11-22 09:38:22 -0800343 gerrit.createProject(project)
344
345 if not os.path.exists(project_dir):
346 run_command("git --bare init %s" % project_dir)
347 run_command("chown -R gerrit2:gerrit2 %s"
348 % project_dir)
349
350 git_command(repo_path,
Clark Boylane0c725b2012-11-30 15:15:55 -0800351 push_string % remote_url,
Monty Taylorda3bada2012-11-22 09:38:22 -0800352 env=ssh_env)
353 git_command(repo_path,
354 "push --tags %s" % remote_url, env=ssh_env)
355 finally:
356 run_command("rm -fr %s" % tmpdir)
357
Jeremy Stanley8b9f93f2013-01-21 02:44:21 +0000358 try:
James E. Blair18e82562013-04-04 16:20:32 +0000359 acl_config = section.get('acl-config',
360 '%s.config' % os.path.join(ACL_DIR,
361 project))
362 except AttributeError:
363 acl_config = None
364
365 if acl_config:
366 if not os.path.isfile(acl_config):
367 write_acl_config(project,
368 ACL_DIR,
369 section.get('acl-base', None),
370 section.get('acl-append', []),
371 section.get('acl-parameters', {}))
372 tmpdir = tempfile.mkdtemp()
373 try:
374 repo_path = os.path.join(tmpdir, 'repo')
375 ret, _ = run_command_status("git init %s" % repo_path)
376 if ret != 0:
377 continue
378 if (fetch_config(project,
379 remote_url,
380 repo_path,
381 ssh_env) and
382 copy_acl_config(project, repo_path,
383 acl_config) and
384 create_groups_file(project, gerrit, repo_path)):
385 push_acl_config(project,
386 remote_url,
387 repo_path,
388 ssh_env)
389 finally:
390 run_command("rm -fr %s" % tmpdir)
Monty Taylorda3bada2012-11-22 09:38:22 -0800391 finally:
392 os.unlink(ssh_env['GIT_SSH'])