blob: fb1712b158d943b5ba63917b6131e6ed1e15e940 [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
Monty Taylor16390e22012-11-04 22:20:36 +010053logging.basicConfig(level=logging.ERROR)
54log = logging.getLogger("manage_projects")
55
56
57def run_command(cmd, status=False, env={}):
58 cmd_list = shlex.split(str(cmd))
59 newenv = os.environ
60 newenv.update(env)
61 log.debug("Executing command: %s" % " ".join(cmd_list))
62 p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE,
63 stderr=subprocess.STDOUT, env=newenv)
64 (out, nothing) = p.communicate()
65 log.debug("Return code: %s" % p.returncode)
66 log.debug("Command said: %s" % out.strip())
67 if status:
68 return (p.returncode, out.strip())
69 return out.strip()
70
71
72def run_command_status(cmd, env={}):
73 return run_command(cmd, True, env)
74
75
76def git_command(repo_dir, sub_cmd, env={}):
77 git_dir = os.path.join(repo_dir, '.git')
78 cmd = "git --git-dir=%s --work-tree=%s %s" % (git_dir, repo_dir, sub_cmd)
79 status, _ = run_command(cmd, True, env)
80 return status
81
82
Clark Boylan808b5132012-11-14 15:52:02 -080083def git_command_output(repo_dir, sub_cmd, env={}):
84 git_dir = os.path.join(repo_dir, '.git')
85 cmd = "git --git-dir=%s --work-tree=%s %s" % (git_dir, repo_dir, sub_cmd)
86 status, out = run_command(cmd, True, env)
87 return (status, out)
88
89
Monty Taylor16390e22012-11-04 22:20:36 +010090def fetch_config(project, remote_url, repo_path, env={}):
91 status = git_command(repo_path, "fetch %s +refs/meta/config:"
92 "refs/remotes/gerrit-meta/config" % remote_url, env)
93 if status != 0:
94 print "Failed to fetch refs/meta/config for project: %s" % project
95 return False
96 # Because the following fails if executed more than once you should only
97 # run fetch_config once in each repo.
98 status = git_command(repo_path, "checkout -b config "
99 "remotes/gerrit-meta/config")
100 if status != 0:
101 print "Failed to checkout config for project: %s" % project
102 return False
103
104 return True
105
106
107def copy_acl_config(project, repo_path, acl_config):
108 if not os.path.exists(acl_config):
109 return False
110
111 acl_dest = os.path.join(repo_path, "project.config")
112 status, _ = run_command("cp %s %s" %
113 (acl_config, acl_dest), status=True)
114 if status == 0:
Monty Taylorda3bada2012-11-22 09:38:22 -0800115 status = git_command(repo_path, "diff-index --quiet HEAD --")
Monty Taylor16390e22012-11-04 22:20:36 +0100116 if status != 0:
117 return True
118 return False
119
120
121def push_acl_config(project, remote_url, repo_path, env={}):
122 cmd = "commit -a -m'Update project config.' --author='Openstack Project " \
Monty Taylorda3bada2012-11-22 09:38:22 -0800123 "Creator <openstack-infra@lists.openstack.org>'"
Monty Taylor16390e22012-11-04 22:20:36 +0100124 status = git_command(repo_path, cmd)
125 if status != 0:
126 print "Failed to commit config for project: %s" % project
127 return False
Clark Boylan808b5132012-11-14 15:52:02 -0800128 status, out = git_command_output(repo_path,
Monty Taylorda3bada2012-11-22 09:38:22 -0800129 "push %s HEAD:refs/meta/config" %
130 remote_url, env)
Monty Taylor16390e22012-11-04 22:20:36 +0100131 if status != 0:
132 print "Failed to push config for project: %s" % project
Clark Boylan808b5132012-11-14 15:52:02 -0800133 print out
Monty Taylor16390e22012-11-04 22:20:36 +0100134 return False
135 return True
136
137
138def _get_group_uuid(gerrit, group):
139 query = "select group_uuid from account_groups where name = '%s'" % group
140 data = gerrit.dbQuery(query)
141 if data:
142 for row in data:
143 if row["type"] == "row":
144 return row["columns"]["group_uuid"]
145 return None
146
147
148def get_group_uuid(gerrit, group):
149 uuid = _get_group_uuid(gerrit, group)
150 if uuid:
151 return uuid
152 gerrit.createGroup(group)
153 uuid = _get_group_uuid(gerrit, group)
154 if uuid:
155 return uuid
156 return None
157
158
159def create_groups_file(project, gerrit, repo_path):
160 acl_config = os.path.join(repo_path, "project.config")
161 group_file = os.path.join(repo_path, "groups")
162 uuids = {}
163 for line in open(acl_config, 'r'):
Clark Boylan5c9670f2012-11-14 17:48:55 -0800164 r = re.match(r'^\s+.*group\s+(.*)$', line)
Monty Taylor16390e22012-11-04 22:20:36 +0100165 if r:
166 group = r.group(1)
167 if group in uuids.keys():
168 continue
169 uuid = get_group_uuid(gerrit, group)
170 if uuid:
171 uuids[group] = uuid
172 else:
173 return False
174 if uuids:
175 with open(group_file, 'w') as fp:
176 for group, uuid in uuids.items():
177 fp.write("%s\t%s\n" % (uuid, group))
178 status = git_command(repo_path, "add groups")
179 if status != 0:
180 print "Failed to add groups file for project: %s" % project
181 return False
182 return True
183
184
185def make_ssh_wrapper(gerrit_user, gerrit_key):
186 (fd, name) = tempfile.mkstemp(text=True)
187 os.write(fd, '#!/bin/bash\n')
188 os.write(fd,
189 'ssh -i %s -l %s -o "StrictHostKeyChecking no" $@\n' %
190 (gerrit_key, gerrit_user))
191 os.close(fd)
192 os.chmod(name, 755)
193 return dict(GIT_SSH=name)
194
195
Monty Taylorda3bada2012-11-22 09:38:22 -0800196def main():
Monty Taylor16390e22012-11-04 22:20:36 +0100197
Monty Taylorda3bada2012-11-22 09:38:22 -0800198 PROJECTS_YAML = os.environ.get('PROJECTS_YAML',
199 '/home/gerrit2/projects.yaml')
200 configs = [config for config in yaml.load_all(open(PROJECTS_YAML))]
201 defaults = configs[0][0]
202 default_has_issues = defaults.get('has-issues', False)
203 default_has_downloads = defaults.get('has-downloads', False)
204 default_has_wiki = defaults.get('has-wiki', False)
Monty Taylor16390e22012-11-04 22:20:36 +0100205
Monty Taylorda3bada2012-11-22 09:38:22 -0800206 LOCAL_GIT_DIR = defaults.get('local-git-dir', '/var/lib/git')
207 GERRIT_HOST = defaults.get('gerrit-host')
208 GERRIT_USER = defaults.get('gerrit-user')
209 GERRIT_KEY = defaults.get('gerrit-key')
Monty Taylor16390e22012-11-04 22:20:36 +0100210
Monty Taylorda3bada2012-11-22 09:38:22 -0800211 GITHUB_SECURE_CONFIG = defaults.get(
212 'github-config',
213 '/etc/github/github-projects.secure.config')
Monty Taylor16390e22012-11-04 22:20:36 +0100214
Monty Taylorda3bada2012-11-22 09:38:22 -0800215 secure_config = ConfigParser.ConfigParser()
216 secure_config.read(GITHUB_SECURE_CONFIG)
Monty Taylor16390e22012-11-04 22:20:36 +0100217
Monty Taylorda3bada2012-11-22 09:38:22 -0800218 # Project creation doesn't work via oauth
219 ghub = github.Github(secure_config.get("github", "username"),
220 secure_config.get("github", "password"))
221 orgs = ghub.get_user().get_orgs()
222 orgs_dict = dict(zip([o.login.lower() for o in orgs], orgs))
Monty Taylor16390e22012-11-04 22:20:36 +0100223
Monty Taylorda3bada2012-11-22 09:38:22 -0800224 gerrit = gerritlib.gerrit.Gerrit('localhost',
225 GERRIT_USER,
226 29418,
227 GERRIT_KEY)
228 project_list = gerrit.listProjects()
229 ssh_env = make_ssh_wrapper(GERRIT_USER, GERRIT_KEY)
230 try:
Monty Taylor16390e22012-11-04 22:20:36 +0100231
Monty Taylorda3bada2012-11-22 09:38:22 -0800232 for section in configs[1]:
233 project = section['project']
234 options = section.get('options', dict())
235 description = section.get('description', None)
236 homepage = section.get('homepage', defaults.get('homepage', None))
237 upstream = section.get('upstream', None)
Monty Taylor16390e22012-11-04 22:20:36 +0100238
Monty Taylorda3bada2012-11-22 09:38:22 -0800239 project_git = "%s.git" % project
240 project_dir = os.path.join(LOCAL_GIT_DIR, project_git)
Monty Taylor16390e22012-11-04 22:20:36 +0100241
Monty Taylorda3bada2012-11-22 09:38:22 -0800242 # Find the project's repo
243 project_split = project.split('/', 1)
244 if len(project_split) > 1:
245 repo_name = project_split[1]
246 else:
247 repo_name = project
248 has_issues = 'has-issues' in options or default_has_issues
249 has_downloads = 'has-downloads' in options or default_has_downloads
250 has_wiki = 'has-wiki' in options or default_has_wiki
Monty Taylor16390e22012-11-04 22:20:36 +0100251 try:
Monty Taylorda3bada2012-11-22 09:38:22 -0800252 org = orgs_dict[project_split[0].lower()]
253 except KeyError:
254 # We do not have control of this github org ignore the project.
255 continue
Monty Taylor16390e22012-11-04 22:20:36 +0100256 try:
Monty Taylorda3bada2012-11-22 09:38:22 -0800257 repo = org.get_repo(repo_name)
258 except github.GithubException:
259 repo = org.create_repo(repo_name,
260 homepage=homepage,
261 has_issues=has_issues,
262 has_downloads=has_downloads,
263 has_wiki=has_wiki)
264 if description:
265 repo.edit(repo_name, description=description)
266 if homepage:
267 repo.edit(repo_name, homepage=homepage)
268
269 repo.edit(repo_name, has_issues=has_issues,
270 has_downloads=has_downloads,
271 has_wiki=has_wiki)
272
273 if 'gerrit' not in [team.name for team in repo.get_teams()]:
274 teams = org.get_teams()
275 teams_dict = dict(zip([t.name.lower() for t in teams], teams))
276 teams_dict['gerrit'].add_to_repos(repo)
277
278 remote_url = "ssh://localhost:29418/%s" % project
279 if project not in project_list:
280 tmpdir = tempfile.mkdtemp()
281 try:
282 repo_path = os.path.join(tmpdir, 'repo')
283 if upstream:
284 run_command("git clone %(upstream)s %(repo_path)s" %
285 dict(upstream=upstream,
286 repo_path=repo_path))
287 else:
288 run_command("git init %s" % repo_path)
289 with open(os.path.join(repo_path,
290 ".gitreview"),
291 'w') as gitreview:
292 gitreview.write("""
293 [gerrit]
294 host=%s
295 port=29418
296 project=%s
297 """ % (GERRIT_HOST, project_git))
298 git_command(repo_path, "add .gitreview")
299 cmd = "commit -a -m'Added .gitreview' --author=" \
300 "'Openstack Project Creator " \
301 "<openstack-infra@lists.openstack.org>'"
302 git_command(repo_path, cmd)
303 gerrit.createProject(project)
304
305 if not os.path.exists(project_dir):
306 run_command("git --bare init %s" % project_dir)
307 run_command("chown -R gerrit2:gerrit2 %s"
308 % project_dir)
309
310 git_command(repo_path,
311 "push --all %s" % remote_url,
312 env=ssh_env)
313 git_command(repo_path,
314 "push --tags %s" % remote_url, env=ssh_env)
315 finally:
316 run_command("rm -fr %s" % tmpdir)
317
318 if 'acl_config' in section:
319 tmpdir = tempfile.mkdtemp()
320 try:
321 repo_path = os.path.join(tmpdir, 'repo')
322 ret, _ = run_command_status("git init %s" % repo_path)
323 if ret != 0:
324 continue
325 if (fetch_config(project,
326 remote_url,
327 repo_path,
328 ssh_env) and
329 copy_acl_config(project, repo_path,
330 section['acl_config']) and
331 create_groups_file(project, gerrit, repo_path)):
332 push_acl_config(project,
333 remote_url,
334 repo_path,
335 ssh_env)
336 finally:
337 run_command("rm -fr %s" % tmpdir)
338 finally:
339 os.unlink(ssh_env['GIT_SSH'])