blob: 0bb0fc749a7b7e6c0abd21f11a90560c82923f0f [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
Clark Boylan808b5132012-11-14 15:52:02 -080084def git_command_output(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, out = run_command(cmd, True, env)
88 return (status, out)
89
90
Monty Taylor16390e22012-11-04 22:20:36 +010091def fetch_config(project, remote_url, repo_path, env={}):
92 status = git_command(repo_path, "fetch %s +refs/meta/config:"
93 "refs/remotes/gerrit-meta/config" % remote_url, env)
94 if status != 0:
95 print "Failed to fetch refs/meta/config for project: %s" % project
96 return False
97 # Because the following fails if executed more than once you should only
98 # run fetch_config once in each repo.
99 status = git_command(repo_path, "checkout -b config "
100 "remotes/gerrit-meta/config")
101 if status != 0:
102 print "Failed to checkout config for project: %s" % project
103 return False
104
105 return True
106
107
108def copy_acl_config(project, repo_path, acl_config):
109 if not os.path.exists(acl_config):
110 return False
111
112 acl_dest = os.path.join(repo_path, "project.config")
113 status, _ = run_command("cp %s %s" %
114 (acl_config, acl_dest), status=True)
115 if status == 0:
116 status = git_command(repo_path, "diff-index --quiet HEAD --")
117 if status != 0:
118 return True
119 return False
120
121
122def push_acl_config(project, remote_url, repo_path, env={}):
123 cmd = "commit -a -m'Update project config.' --author='Openstack Project " \
124 "Creator <openstack-infra@lists.openstack.org>'"
125 status = git_command(repo_path, cmd)
126 if status != 0:
127 print "Failed to commit config for project: %s" % project
128 return False
Clark Boylan808b5132012-11-14 15:52:02 -0800129 status, out = git_command_output(repo_path,
Monty Taylor16390e22012-11-04 22:20:36 +0100130 "push %s HEAD:refs/meta/config" %
131 remote_url, env)
132 if status != 0:
133 print "Failed to push config for project: %s" % project
Clark Boylan808b5132012-11-14 15:52:02 -0800134 print out
Monty Taylor16390e22012-11-04 22:20:36 +0100135 return False
136 return True
137
138
139def _get_group_uuid(gerrit, group):
140 query = "select group_uuid from account_groups where name = '%s'" % group
141 data = gerrit.dbQuery(query)
142 if data:
143 for row in data:
144 if row["type"] == "row":
145 return row["columns"]["group_uuid"]
146 return None
147
148
149def get_group_uuid(gerrit, group):
150 uuid = _get_group_uuid(gerrit, group)
151 if uuid:
152 return uuid
153 gerrit.createGroup(group)
154 uuid = _get_group_uuid(gerrit, group)
155 if uuid:
156 return uuid
157 return None
158
159
160def create_groups_file(project, gerrit, repo_path):
161 acl_config = os.path.join(repo_path, "project.config")
162 group_file = os.path.join(repo_path, "groups")
163 uuids = {}
164 for line in open(acl_config, 'r'):
Clark Boylan5c9670f2012-11-14 17:48:55 -0800165 r = re.match(r'^\s+.*group\s+(.*)$', line)
Monty Taylor16390e22012-11-04 22:20:36 +0100166 if r:
167 group = r.group(1)
168 if group in uuids.keys():
169 continue
170 uuid = get_group_uuid(gerrit, group)
171 if uuid:
172 uuids[group] = uuid
173 else:
174 return False
175 if uuids:
176 with open(group_file, 'w') as fp:
177 for group, uuid in uuids.items():
178 fp.write("%s\t%s\n" % (uuid, group))
179 status = git_command(repo_path, "add groups")
180 if status != 0:
181 print "Failed to add groups file for project: %s" % project
182 return False
183 return True
184
185
186def make_ssh_wrapper(gerrit_user, gerrit_key):
187 (fd, name) = tempfile.mkstemp(text=True)
188 os.write(fd, '#!/bin/bash\n')
189 os.write(fd,
190 'ssh -i %s -l %s -o "StrictHostKeyChecking no" $@\n' %
191 (gerrit_key, gerrit_user))
192 os.close(fd)
193 os.chmod(name, 755)
194 return dict(GIT_SSH=name)
195
196
197PROJECTS_YAML = os.environ.get('PROJECTS_YAML',
198 '/home/gerrit2/projects.yaml')
199configs = [config for config in yaml.load_all(open(PROJECTS_YAML))]
200defaults = configs[0][0]
201default_has_issues = defaults.get('has-issues', False)
202default_has_downloads = defaults.get('has-downloads', False)
203default_has_wiki = defaults.get('has-wiki', False)
204
205LOCAL_GIT_DIR = defaults.get('local-git-dir', '/var/lib/git')
206GERRIT_HOST = defaults.get('gerrit-host')
207GERRIT_USER = defaults.get('gerrit-user')
208GERRIT_KEY = defaults.get('gerrit-key')
209GITHUB_SECURE_CONFIG = defaults.get('github-config',
210 '/etc/github/github-projects.secure.config')
211
212secure_config = ConfigParser.ConfigParser()
213secure_config.read(GITHUB_SECURE_CONFIG)
214
215# Project creation doesn't work via oauth
216ghub = github.Github(secure_config.get("github", "username"),
217 secure_config.get("github", "password"))
218orgs = ghub.get_user().get_orgs()
219orgs_dict = dict(zip([o.login.lower() for o in orgs], orgs))
220
221gerrit = gerritlib.gerrit.Gerrit('localhost',
222 GERRIT_USER,
223 29418,
224 GERRIT_KEY)
225project_list = gerrit.listProjects()
226ssh_env = make_ssh_wrapper(GERRIT_USER, GERRIT_KEY)
227try:
228
229 for section in configs[1]:
230 project = section['project']
231 options = section.get('options', dict())
232 description = section.get('description', None)
233 homepage = section.get('homepage', defaults.get('homepage', None))
234 upstream = section.get('upstream', None)
235
236 project_git = "%s.git" % project
237 project_dir = os.path.join(LOCAL_GIT_DIR, project_git)
238
239 # Find the project's repo
240 project_split = project.split('/', 1)
241 if len(project_split) > 1:
242 repo_name = project_split[1]
243 else:
244 repo_name = project
245 has_issues = 'has-issues' in options or default_has_issues
246 has_downloads = 'has-downloads' in options or default_has_downloads
247 has_wiki = 'has-wiki' in options or default_has_wiki
Clark Boylan9eac8812012-11-14 09:21:39 -0800248 try:
249 org = orgs_dict[project_split[0].lower()]
250 except KeyError:
251 # We do not have control of this github org ignore the project.
252 continue
Monty Taylor16390e22012-11-04 22:20:36 +0100253 try:
254 repo = org.get_repo(repo_name)
255 except github.GithubException:
256 repo = org.create_repo(repo_name,
257 homepage=homepage,
258 has_issues=has_issues,
259 has_downloads=has_downloads,
260 has_wiki=has_wiki)
261 if description:
262 repo.edit(repo_name, description=description)
263 if homepage:
264 repo.edit(repo_name, homepage=homepage)
265
266 repo.edit(repo_name, has_issues=has_issues,
267 has_downloads=has_downloads,
268 has_wiki=has_wiki)
269
270 if 'gerrit' not in [team.name for team in repo.get_teams()]:
271 teams = org.get_teams()
272 teams_dict = dict(zip([t.name.lower() for t in teams], teams))
273 teams_dict['gerrit'].add_to_repos(repo)
274
275 remote_url = "ssh://localhost:29418/%s" % project
276 if project not in project_list:
277 tmpdir = tempfile.mkdtemp()
278 try:
279 repo_path = os.path.join(tmpdir, 'repo')
280 if upstream:
Clark Boylan5c9670f2012-11-14 17:48:55 -0800281 run_command("git clone %(upstream)s %(repo_path)s" %
Monty Taylor16390e22012-11-04 22:20:36 +0100282 dict(upstream=upstream, repo_path=repo_path))
283 else:
284 run_command("git init %s" % repo_path)
285 with open(os.path.join(repo_path,
286 ".gitreview"), 'w') as gitreview:
287 gitreview.write("""
288[gerrit]
289host=%s
290port=29418
291project=%s
292""" % (GERRIT_HOST, project_git))
293 git_command(repo_path, "add .gitreview")
Clark Boylan33c46d42012-11-14 08:53:37 -0800294 cmd = "commit -a -m'Added .gitreview' --author=" \
295 "'Openstack Project Creator " \
296 "<openstack-infra@lists.openstack.org>'"
297 git_command(repo_path, cmd)
Monty Taylor16390e22012-11-04 22:20:36 +0100298 gerrit.createProject(project)
299
300 if not os.path.exists(project_dir):
301 run_command("git --bare init %s" % project_dir)
302 run_command("chown -R gerrit2:gerrit2 %s" % project_dir)
303
304 git_command(repo_path,
Monty Taylor5003e162012-11-15 11:12:53 -0800305 "push --all %s" % remote_url,
Monty Taylor16390e22012-11-04 22:20:36 +0100306 env=ssh_env)
307 git_command(repo_path,
308 "push --tags %s" % remote_url, env=ssh_env)
309 finally:
310 run_command("rm -fr %s" % tmpdir)
311
312 if 'acl_config' in section:
313 tmpdir = tempfile.mkdtemp()
314 try:
315 repo_path = os.path.join(tmpdir, 'repo')
316 ret, _ = run_command_status("git init %s" % repo_path)
317 if ret != 0:
318 continue
319 if (fetch_config(project, remote_url, repo_path, ssh_env) and
320 copy_acl_config(project, repo_path,
321 section['acl_config']) and
322 create_groups_file(project, gerrit, repo_path)):
323 push_acl_config(project, remote_url, repo_path, ssh_env)
324 finally:
325 run_command("rm -fr %s" % tmpdir)
326finally:
327 os.unlink(ssh_env['GIT_SSH'])