Monty Taylor | fa74302 | 2017-02-14 07:36:34 -0600 | [diff] [blame] | 1 | #! /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 config file called projects.ini |
| 18 | # It should look like: |
| 19 | |
| 20 | # [projects] |
| 21 | # homepage=http://openstack.org |
| 22 | # gerrit-host=review.openstack.org |
| 23 | # local-git-dir=/var/lib/git |
| 24 | # gerrit-key=/home/gerrit2/review_site/etc/ssh_host_rsa_key |
| 25 | # gerrit-committer=Project Creator <openstack-infra@lists.openstack.org> |
| 26 | # gerrit-replicate=True |
| 27 | # has-github=True |
| 28 | # has-wiki=False |
| 29 | # has-issues=False |
| 30 | # has-downloads=False |
| 31 | # acl-dir=/home/gerrit2/acls |
| 32 | # acl-base=/home/gerrit2/acls/project.config |
| 33 | # |
| 34 | # manage_projects.py reads a project listing file called projects.yaml |
| 35 | # It should look like: |
| 36 | # - project: PROJECT_NAME |
| 37 | # options: |
| 38 | # - has-wiki |
| 39 | # - has-issues |
| 40 | # - has-downloads |
| 41 | # - has-pull-requests |
| 42 | # - track-upstream |
| 43 | # homepage: Some homepage that isn't http://openstack.org |
| 44 | # description: This is a great project |
| 45 | # upstream: https://gerrit.googlesource.com/gerrit |
| 46 | # upstream-prefix: upstream |
| 47 | # acl-config: /path/to/gerrit/project.config |
| 48 | # acl-append: |
| 49 | # - /path/to/gerrit/project.config |
| 50 | # acl-parameters: |
| 51 | # project: OTHER_PROJECT_NAME |
| 52 | |
| 53 | import argparse |
| 54 | import json |
| 55 | import logging |
| 56 | import os |
| 57 | |
| 58 | import gerritlib.gerrit |
| 59 | |
| 60 | import jeepyb.log as l |
| 61 | import jeepyb.utils as u |
| 62 | |
| 63 | registry = u.ProjectsRegistry() |
| 64 | |
| 65 | log = logging.getLogger("track_upstream") |
| 66 | orgs = None |
| 67 | |
| 68 | |
Monty Taylor | fa74302 | 2017-02-14 07:36:34 -0600 | [diff] [blame] | 69 | def update_local_copy(repo_path, track_upstream, git_opts, ssh_env): |
| 70 | # first do a clean of the branch to prevent possible |
| 71 | # problems due to previous runs |
| 72 | u.git_command(repo_path, "clean -fdx") |
| 73 | |
| 74 | has_upstream_remote = ( |
| 75 | 'upstream' in u.git_command_output(repo_path, 'remote')[1]) |
| 76 | if track_upstream: |
| 77 | # If we're configured to track upstream but the repo |
| 78 | # does not have an upstream remote, add one |
| 79 | if not has_upstream_remote: |
| 80 | u.git_command( |
| 81 | repo_path, |
| 82 | "remote add upstream %(upstream)s" % git_opts) |
| 83 | |
| 84 | # If we're configured to track upstream, make sure that |
| 85 | # the upstream URL matches the config |
| 86 | else: |
| 87 | u.git_command( |
| 88 | repo_path, |
| 89 | "remote set-url upstream %(upstream)s" % git_opts) |
| 90 | |
| 91 | # Now that we have any upstreams configured, fetch all of the refs |
| 92 | # we might need, pruning remote branches that no longer exist |
| 93 | u.git_command( |
| 94 | repo_path, "remote update --prune", env=ssh_env) |
| 95 | else: |
| 96 | # If we are not tracking upstream, then we do not need |
| 97 | # an upstream remote configured |
| 98 | if has_upstream_remote: |
| 99 | u.git_command(repo_path, "remote rm upstream") |
| 100 | |
| 101 | # TODO(mordred): This is here so that later we can |
| 102 | # inspect the master branch for meta-info |
| 103 | # Checkout master and reset to the state of origin/master |
| 104 | u.git_command(repo_path, "checkout -B master origin/master") |
| 105 | |
| 106 | |
| 107 | def fsck_repo(repo_path): |
| 108 | rc, out = u.git_command_output(repo_path, 'fsck --full') |
| 109 | # Check for non zero return code or warnings which should |
| 110 | # be treated as errors. In this case zeroPaddedFilemodes |
| 111 | # will not be accepted by Gerrit/jgit but are accepted by C git. |
| 112 | if rc != 0 or 'zeroPaddedFilemode' in out: |
| 113 | log.error('git fsck of %s failed:\n%s' % (repo_path, out)) |
| 114 | raise Exception('git fsck failed not importing') |
| 115 | |
| 116 | |
| 117 | def push_to_gerrit(repo_path, project, push_string, remote_url, ssh_env): |
| 118 | try: |
| 119 | u.git_command(repo_path, push_string % remote_url, env=ssh_env) |
| 120 | u.git_command(repo_path, "push --tags %s" % remote_url, env=ssh_env) |
| 121 | except Exception: |
| 122 | log.exception( |
| 123 | "Error pushing %s to Gerrit." % project) |
| 124 | |
| 125 | |
| 126 | def sync_upstream(repo_path, project, ssh_env, upstream_prefix): |
| 127 | u.git_command( |
| 128 | repo_path, |
| 129 | "remote update upstream --prune", env=ssh_env) |
| 130 | # Any branch that exists in the upstream remote, we want |
| 131 | # a local branch of, optionally prefixed with the |
| 132 | # upstream prefix value |
| 133 | for branch in u.git_command_output( |
| 134 | repo_path, "branch -a")[1].split('\n'): |
| 135 | if not branch.strip().startswith("remotes/upstream"): |
| 136 | continue |
| 137 | if "->" in branch: |
| 138 | continue |
| 139 | local_branch = branch.split()[0][len('remotes/upstream/'):] |
| 140 | if upstream_prefix: |
| 141 | local_branch = "%s/%s" % ( |
| 142 | upstream_prefix, local_branch) |
| 143 | |
| 144 | # Check out an up to date copy of the branch, so that |
| 145 | # we can push it and it will get picked up below |
| 146 | u.git_command( |
| 147 | repo_path, "checkout -B %s %s" % (local_branch, branch)) |
| 148 | |
| 149 | try: |
| 150 | # Push all of the local branches to similarly named |
| 151 | # Branches on gerrit. Also, push all of the tags |
| 152 | u.git_command( |
| 153 | repo_path, |
| 154 | "push origin refs/heads/*:refs/heads/*", |
| 155 | env=ssh_env) |
| 156 | u.git_command(repo_path, 'push origin --tags', env=ssh_env) |
| 157 | except Exception: |
| 158 | log.exception( |
| 159 | "Error pushing %s to Gerrit." % project) |
| 160 | |
| 161 | |
| 162 | def main(): |
| 163 | parser = argparse.ArgumentParser(description='Manage projects') |
| 164 | l.setup_logging_arguments(parser) |
| 165 | parser.add_argument('--nocleanup', action='store_true', |
| 166 | help='do not remove temp directories') |
| 167 | parser.add_argument('projects', metavar='project', nargs='*', |
| 168 | help='name of project(s) to process') |
| 169 | args = parser.parse_args() |
| 170 | l.configure_logging(args) |
| 171 | |
| 172 | JEEPYB_CACHE_DIR = registry.get_defaults('jeepyb-cache-dir', |
| 173 | '/var/lib/jeepyb') |
| 174 | IMPORT_DIR = os.path.join(JEEPYB_CACHE_DIR, 'import') |
| 175 | GERRIT_HOST = registry.get_defaults('gerrit-host') |
| 176 | GERRIT_PORT = int(registry.get_defaults('gerrit-port', '29418')) |
| 177 | GERRIT_USER = registry.get_defaults('gerrit-user') |
| 178 | GERRIT_KEY = registry.get_defaults('gerrit-key') |
| 179 | GERRIT_GITID = registry.get_defaults('gerrit-committer') |
| 180 | |
| 181 | PROJECT_CACHE_FILE = os.path.join(JEEPYB_CACHE_DIR, 'project.cache') |
| 182 | project_cache = {} |
| 183 | if os.path.exists(PROJECT_CACHE_FILE): |
| 184 | project_cache = json.loads(open(PROJECT_CACHE_FILE, 'r').read()) |
| 185 | |
| 186 | gerrit = gerritlib.gerrit.Gerrit(GERRIT_HOST, |
| 187 | GERRIT_USER, |
| 188 | GERRIT_PORT, |
| 189 | GERRIT_KEY) |
| 190 | project_list = gerrit.listProjects() |
| 191 | ssh_env = u.make_ssh_wrapper(GERRIT_USER, GERRIT_KEY) |
| 192 | try: |
| 193 | |
| 194 | for section in registry.configs_list: |
| 195 | project = section['project'] |
| 196 | if args.projects and project not in args.projects: |
| 197 | continue |
| 198 | |
| 199 | try: |
| 200 | log.info("Processing project: %s" % project) |
| 201 | |
| 202 | # Figure out all of the options |
| 203 | options = section.get('options', dict()) |
| 204 | track_upstream = 'track-upstream' in options |
| 205 | if not track_upstream: |
| 206 | continue |
| 207 | |
| 208 | # If this project doesn't want to use gerrit, exit cleanly. |
| 209 | if 'no-gerrit' in options: |
| 210 | continue |
| 211 | |
| 212 | upstream = section.get('upstream', None) |
| 213 | upstream_prefix = section.get('upstream-prefix', None) |
| 214 | repo_path = os.path.join(IMPORT_DIR, project) |
| 215 | |
| 216 | project_git = "%s.git" % project |
| 217 | remote_url = "ssh://%s:%s/%s" % ( |
| 218 | GERRIT_HOST, |
| 219 | GERRIT_PORT, |
| 220 | project) |
| 221 | git_opts = dict(upstream=upstream, |
| 222 | repo_path=repo_path, |
| 223 | remote_url=remote_url) |
| 224 | project_cache.setdefault(project, {}) |
| 225 | if not project_cache[project]['pushed-to-gerrit']: |
| 226 | continue |
| 227 | |
| 228 | # Make Local repo |
| 229 | if not os.path.exists(repo_path): |
Monty Taylor | 36f4fa4 | 2017-02-14 12:24:13 -0600 | [diff] [blame] | 230 | u.make_local_copy( |
Monty Taylor | fa74302 | 2017-02-14 07:36:34 -0600 | [diff] [blame] | 231 | repo_path, project, project_list, |
| 232 | git_opts, ssh_env, upstream, GERRIT_HOST, |
| 233 | GERRIT_PORT, project_git, GERRIT_GITID) |
| 234 | else: |
| 235 | update_local_copy( |
| 236 | repo_path, track_upstream, git_opts, ssh_env) |
| 237 | |
| 238 | fsck_repo(repo_path) |
| 239 | sync_upstream(repo_path, project, ssh_env, upstream_prefix) |
| 240 | |
| 241 | except Exception: |
| 242 | log.exception( |
| 243 | "Problems creating %s, moving on." % project) |
| 244 | continue |
| 245 | finally: |
| 246 | os.unlink(ssh_env['GIT_SSH']) |
| 247 | |
| 248 | if __name__ == "__main__": |
| 249 | main() |