project-config/tools/check_valid_gerrit_projects.py
Andreas Jaeger 5c074b977b Avoid import of zuul files
Check that new upstream projects do not have any zuul files in there and
fail if so.

We add this check to not import a zuul config file that is broken. We
only accept zuul config files that have been gone through review.

Change-Id: Ie33d91637e812a17f3b2a2b2c75cbbadcde176ad
2018-02-21 21:16:24 +01:00

239 lines
9.0 KiB
Python
Executable File

#!/usr/bin/env python
#
# Check that gerrit/projects.yaml contains valid entries.
#
# 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.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import contextlib
import git
import os
import re
import shutil
import sys
import tempfile
import yaml
@contextlib.contextmanager
def tempdir():
try:
reqroot = tempfile.mkdtemp()
yield reqroot
finally:
shutil.rmtree(reqroot, ignore_errors=True)
def check_repo(repo_path):
found_errors = 0
print("Checking git repo '%s':" % repo_path)
with tempdir() as repopath:
repo = git.Repo.clone_from(repo_path, repopath)
remotes = repo.git.branch('--remote')
branches = [r.strip() for r in remotes.splitlines() if r.strip()]
print(" Remote branches:")
for r in branches:
print(" %s" % r)
if 'origin/master' in branches:
print(" Master branch exists.")
else:
found_errors += 1
print(" ERROR: No master branch exists")
if 'origin/stable' in branches:
found_errors += 1
print(" ERROR: A branch named 'stable' exists, this will"
" break future\n"
" creation of stable/RELEASEbranches.\n"
" Delete the branch on your upstream project.")
if 'origin/feature' in branches:
found_errors += 1
print(" ERROR: A branch named 'feature' exists, this will break "
"future\n"
" creation of feature/NAME branches.\n"
" Delete the branch on your upstream project.")
if repo.tags:
print(" Found the following tags:")
for tag in repo.tags:
print(" %s" % tag)
else:
print(" Found no tags.")
# Check that no zuul files are in here
for branch in branches:
print("Testing branch %s" % branch)
if 'origin/HEAD' in branch:
continue
repo.git.checkout(branch)
head = repo.head.commit.tree
for z in ['zuul.yaml', 'zuul.yaml', 'zuul.d', '.zuul.d']:
if z in head:
found_errors += 1
print(" ERROR: Found %s on branch %s" % (z, branch))
print(" Remove any zuul config files before import.")
# Just an empty line for nicer formatting
print("")
return found_errors
def main():
found_errors = 0
parser = argparse.ArgumentParser()
parser.add_argument('-v', '--verbose',
dest='verbose',
default=False,
action='store_true')
parser.add_argument(
'infile',
help='Path to gerrit/projects.yaml',
)
parser.add_argument(
'acldir',
help='Path to gerrit/acl',
)
args = parser.parse_args()
projects = yaml.load(open(args.infile, 'r'))
VALID_LABELS = ["acl-config", "description", "docimpact-group",
"groups", "homepage", "options", "project",
"upstream", "upstream-prefix", "use-storyboard"]
VALID_SCHEMES = ['https://', 'http://', 'git://']
DESCRIPTION_REQUIRED = ['openstack', 'openstack-infra', 'openstack-dev',
'stackforge']
VALID_OPTIONS = ['delay-release', 'track-upstream', 'translate']
for p in projects:
name = p.get('project')
repo_group, repo_name = name.split('/')
if not name:
# not a project
found_errors += 1
print("ERROR: Entry is not a project %s" % p)
continue
if args.verbose:
print('Checking %s' % name)
description = p.get('description')
# *very* simple check for common description mistakes
badwords = (
# (words), what_words_should_be
(('openstack', 'Openstack', 'Open Stack'), 'OpenStack'),
(('Devstack', 'devstack'), 'DevStack'),
(('astor', 'Astor', 'astra', 'Astra', 'astara'), 'Astara')
)
if description:
# newlines here mess up cgit "repo.desc
if '\n' in description:
found_errors += 1
print("ERROR: Descriptions should not contain newlines:")
print(' "%s"' % description)
for words, should_be in badwords:
for word in words:
# look for the bad word hanging out on it's own. Only
# trick is "\b" doesn't consider "-" or '.' as a
# word-boundary, so ignore it if it looks like some
# sort of job-description (e.g. "foo-devstack-bar") or
# a url ("foo.openstack.org")
if re.search(r'(?<![-.])\b%s\b' % word, description):
print("ERROR: project %s, description '%s': "
"contains wrong word '%s', it should be '%s'" %
(name, description, word, should_be))
found_errors += 1
if not description and repo_group in DESCRIPTION_REQUIRED:
found_errors += 1
print("ERROR: Project %s has no description" % name)
continue
# Check upstream URL
# Allow git:// and https:// URLs for importing upstream repositories,
# but not git@
upstream = p.get('upstream')
if upstream and 'track-upstream' not in p.get('options', []):
openstack_repo = 'https://git.openstack.org/%s' % name
try:
# Check to see if we have already imported the project into
# OpenStack, if so skip checking upstream.
check_repo(openstack_repo)
except git.exc.GitCommandError:
# We haven't imported the repo yet, make sure upstream is
# valid.
found_errors += check_repo(upstream)
if upstream:
for prefix in VALID_SCHEMES:
if upstream.startswith(prefix):
break
else:
found_errors += 1
print('ERROR: Upstream URLs should use a scheme in %s, '
'found %s in %s' %
(VALID_SCHEMES, p['upstream'], name))
# Check for any wrong entries
for entry in p:
for label in VALID_LABELS:
if entry == label:
break
else:
found_errors += 1
print("ERROR: Unknown keyword '%s' in project %s" %
(entry, name))
# Check for valid options
for option in p.get('options', []):
if option not in VALID_OPTIONS:
found_errors += 1
print("ERROR: Unknown option '%s' in project %s" %
(option, name))
# Check redundant acl-config
acl_config = p.get('acl-config')
if acl_config:
if acl_config.endswith(name + '.config'):
found_errors += 1
print("ERROR: Project %s has redundant acl_config line, "
"remove it." % name)
if not acl_config.startswith('/home/gerrit2/acls/'):
found_errors += 1
print("ERROR: Project %s has wrong acl_config line, "
"fix the path." % name)
acl_file = os.path.join(args.acldir,
acl_config[len('/home/gerrit2/acls/'):])
if not os.path.isfile(acl_file):
found_errors += 1
print("ERROR: Project %s has non existing acl_config line" %
name)
else:
# Check that default file exists
acl_file = os.path.join(args.acldir, name + ".config")
if not os.path.isfile(acl_file):
found_errors += 1
print("ERROR: Project %s has no default acl-config file" %
name)
# Check redundant groups entry:
# By default the groups entry is repo_name, no need to add this.
groups = p.get('groups')
storyboard = p.get('use-storyboard', False)
if (groups and len(groups) == 1 and groups[0] == repo_name
and not storyboard):
found_errors += 1
print("ERROR: Non-StoryBoard project %s has default groups entry, "
"remove it" % name)
if found_errors:
print("Found %d error(s) in %s" % (found_errors, args.infile))
sys.exit(1)
if __name__ == '__main__':
main()