220 lines
8.4 KiB
Plaintext
220 lines
8.4 KiB
Plaintext
|
#!/usr/bin/python3
|
||
|
|
||
|
# Copyright (c) 2019 OpenStack Foundation
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||
|
# implied.
|
||
|
|
||
|
import os
|
||
|
import re
|
||
|
import shutil
|
||
|
import subprocess
|
||
|
import sys
|
||
|
import tempfile
|
||
|
|
||
|
import yaml
|
||
|
|
||
|
|
||
|
def run(commandlist):
|
||
|
"""Wrapper to run a shell command and return a list of stdout lines."""
|
||
|
(o, x) = subprocess.Popen(
|
||
|
commandlist, env=gitenv, stdout=subprocess.PIPE,
|
||
|
stderr=subprocess.DEVNULL).communicate()
|
||
|
return o.decode('utf-8').strip().split('\n')
|
||
|
|
||
|
|
||
|
class EncryptedPKCS1_OAEP(yaml.YAMLObject):
|
||
|
"""Causes pyyaml to skip custom YAML tags Zuul groks."""
|
||
|
yaml_tag = u'!encrypted/pkcs1-oaep'
|
||
|
yaml_loader = yaml.SafeLoader
|
||
|
|
||
|
def __init__(self, x):
|
||
|
pass
|
||
|
|
||
|
@classmethod
|
||
|
def from_yaml(cls, loader, node):
|
||
|
return cls(node.value)
|
||
|
|
||
|
|
||
|
# the gerrit git directory
|
||
|
top = sys.argv[1]
|
||
|
|
||
|
# the repo renames file and a corresponding regex for finding them
|
||
|
renames = {}
|
||
|
for repo in yaml.safe_load(open(sys.argv[2]))['repos']:
|
||
|
renames[repo['old']] = repo['new']
|
||
|
renames_regex = re.compile(
|
||
|
'([^a-z0-9_-]|^)(%s)([^a-z0-9_-]|$)' % '|'.join(renames.keys()))
|
||
|
|
||
|
# our custom git author/committer used by the run function
|
||
|
gitenv = dict(os.environ)
|
||
|
gitenv.update({
|
||
|
'GIT_AUTHOR_NAME': 'OpenDev Sysadmins',
|
||
|
'GIT_AUTHOR_EMAIL': 'openstack-infra@lists.openstack.org',
|
||
|
'GIT_COMMITTER_NAME': 'OpenDev Sysadmins',
|
||
|
'GIT_COMMITTER_EMAIL': 'openstack-infra@lists.openstack.org',
|
||
|
})
|
||
|
|
||
|
# commit message string for generated commits
|
||
|
commit_message = """\
|
||
|
OpenDev Migration Patch
|
||
|
|
||
|
This commit was bulk generated and pushed by the OpenDev sysadmins
|
||
|
as a part of the Git hosting and code review systems migration
|
||
|
detailed in these mailing list posts:
|
||
|
|
||
|
http://lists.openstack.org/pipermail/openstack-discuss/2019-March/003603.html
|
||
|
http://lists.openstack.org/pipermail/openstack-discuss/2019-April/004920.html
|
||
|
|
||
|
Attempts have been made to correct repository namespaces and
|
||
|
hostnames based on simple pattern matching, but it's possible some
|
||
|
were updated incorrectly or missed entirely. Please reach out to us
|
||
|
via the contact information listed at https://opendev.org/ with any
|
||
|
questions you may have.
|
||
|
"""
|
||
|
|
||
|
# find all second-level directories on which we will operate
|
||
|
repos = run(['find', top, '-maxdepth', '2', '-mindepth', '2', '-name', '*.git', '-type', 'd'])
|
||
|
|
||
|
# iterate over each repo
|
||
|
for bare in repos:
|
||
|
# clone the repo into a temporary working tree
|
||
|
with tempfile.TemporaryDirectory() as repodir:
|
||
|
run(['git', 'clone', bare, repodir])
|
||
|
origdir = os.getcwd()
|
||
|
os.chdir(repodir)
|
||
|
|
||
|
# build a list of branches for this repo
|
||
|
branches = []
|
||
|
branchdump = run(['git', 'branch', '-a'])
|
||
|
|
||
|
# iterate over each branch
|
||
|
for line in branchdump:
|
||
|
branch = re.match('^remotes/origin/([^ ]+)$', line.strip())
|
||
|
if branch:
|
||
|
branches.append(branch.group(1))
|
||
|
for branch in branches:
|
||
|
run(['git', 'checkout', '-B', branch, 'origin/' + branch])
|
||
|
|
||
|
# build up a list of files to edit
|
||
|
editfiles = set()
|
||
|
|
||
|
# find zuul configs and add ansible playbooks they reference
|
||
|
zuulfiles = run([
|
||
|
'find', '.zuul.d/', 'zuul.d/', '.zuul.yaml', 'zuul.yaml',
|
||
|
'-name', '*.yaml', '-type', 'f'])
|
||
|
for zuulfile in zuulfiles:
|
||
|
if zuulfile:
|
||
|
conf = yaml.safe_load(open(zuulfile))
|
||
|
if not conf:
|
||
|
# some repos have empty zuul configs
|
||
|
continue
|
||
|
for node in conf:
|
||
|
if 'job' in node:
|
||
|
for subnode in ('post-run', 'pre-run', 'run'):
|
||
|
if subnode in node['job']:
|
||
|
if type(node['job'][subnode]) is list:
|
||
|
editfiles.update(node['job'][subnode])
|
||
|
else:
|
||
|
editfiles.add(node['job'][subnode])
|
||
|
|
||
|
# if there are roles dirs relative to the playbooks, add them too
|
||
|
for playbook in list(editfiles):
|
||
|
rolesdir = os.path.join(os.path.dirname(playbook), 'roles')
|
||
|
if os.path.isdir(rolesdir):
|
||
|
editfiles.update(run([
|
||
|
'find', rolesdir, '-type', 'f', '(', '-name', '*.j2',
|
||
|
'-o', '-name', '*.yaml', '-o', '-name', '*.yml', ')']))
|
||
|
|
||
|
# zuul looks at the top level roles dir too
|
||
|
editfiles.update(run([
|
||
|
'find', 'roles', '-type', 'f', '(', '-name', '*.j2', '-o',
|
||
|
'-name', '*.yaml', '-o', '-name', '*.yml', ')']))
|
||
|
|
||
|
# and add the zuul configs themselves
|
||
|
editfiles.update(zuulfiles)
|
||
|
|
||
|
# and add .gitreview of course
|
||
|
editfiles.add('.gitreview')
|
||
|
|
||
|
# and zuul/main.yaml so we catch the tenant config
|
||
|
editfiles.add('zuul/main.yaml')
|
||
|
|
||
|
# and gerrit/projects.yaml for manage-projects
|
||
|
editfiles.add('gerrit/projects.yaml')
|
||
|
|
||
|
# and gerritbot/channels.yaml for gerritbot
|
||
|
editfiles.add('gerritbot/channels.yaml')
|
||
|
|
||
|
# drop any empty filename we ended up with
|
||
|
editfiles.discard('')
|
||
|
|
||
|
# read through each file and replace specific patterns
|
||
|
for fname in editfiles:
|
||
|
if not os.path.exists(fname):
|
||
|
continue
|
||
|
with open(fname) as rfd, tempfile.NamedTemporaryFile() as wfd:
|
||
|
# track modifications for efficiency
|
||
|
modified = False
|
||
|
for line in rfd:
|
||
|
# apply renames from the mapping
|
||
|
found = renames_regex.search(line)
|
||
|
while found:
|
||
|
line = line.replace(
|
||
|
found.group(2), renames[found.group(2)])
|
||
|
modified = True
|
||
|
found = renames_regex.search(line)
|
||
|
|
||
|
# same for git.openstack.org -> opendev.org
|
||
|
found = re.search("git\.openstack\.org", line)
|
||
|
while found:
|
||
|
line = line.replace(
|
||
|
"git.openstack.org", "opendev.org")
|
||
|
modified = True
|
||
|
found = renames_regex.search(line)
|
||
|
|
||
|
# and review.openstack.org -> review.opendev.org
|
||
|
found = re.search("review\.openstack\.org", line)
|
||
|
while found:
|
||
|
line = line.replace(
|
||
|
"review.openstack.org", "review.opendev.org")
|
||
|
modified = True
|
||
|
found = renames_regex.search(line)
|
||
|
|
||
|
wfd.write(line.encode('utf-8'))
|
||
|
|
||
|
# copy any modified file back into the worktree
|
||
|
if modified:
|
||
|
wfd.flush()
|
||
|
shutil.copyfile(wfd.name, fname)
|
||
|
modified = False
|
||
|
|
||
|
# special logic to rename Gerrit ACL files
|
||
|
if bare.endswith('/project-config.git'):
|
||
|
for acl in run(['git', 'ls-files', 'gerrit/acls/']):
|
||
|
found = renames_regex.search(acl)
|
||
|
if found:
|
||
|
newpath = acl.replace(
|
||
|
found.group(2), renames[found.group(2)])
|
||
|
os.makedirs(os.path.dirname(newpath), exist_ok=True)
|
||
|
run(['git', 'mv', acl, newpath])
|
||
|
|
||
|
# commit and push our changes, if there are any
|
||
|
if run(['git', 'diff']):
|
||
|
with tempfile.NamedTemporaryFile() as message:
|
||
|
message.write(commit_message.encode('utf-8'))
|
||
|
message.flush()
|
||
|
run(['git', 'commit', '-a', '-F', message.name])
|
||
|
run(['git', 'push', 'origin', 'HEAD'])
|
||
|
|
||
|
# switch back before the context manager deletes our cwd
|
||
|
os.chdir(origdir)
|