2020-06-08 16:40:44 -07:00
|
|
|
#!/usr/bin/env python3
|
2017-01-10 11:00:27 +08:00
|
|
|
# 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.
|
2014-05-09 14:53:14 +00:00
|
|
|
|
|
|
|
# Usage: normalize_acl.py acl.config [transformation [transformation [...]]]
|
|
|
|
#
|
|
|
|
# Transformations:
|
2014-12-10 20:37:09 +01:00
|
|
|
# all Apply all transformations.
|
2014-05-09 14:53:14 +00:00
|
|
|
# 0 - dry run (default, print to stdout rather than modifying file in place)
|
|
|
|
# 1 - strip/condense whitespace and sort (implied by any other transformation)
|
|
|
|
# 2 - get rid of unneeded create on refs/tags
|
|
|
|
# 3 - remove any project.stat{e,us} = active since it's a default or a typo
|
|
|
|
# 4 - strip default *.owner = group Administrators permissions
|
|
|
|
# 5 - sort the exclusiveGroupPermissions group lists
|
2014-06-30 17:30:45 +00:00
|
|
|
# 6 - replace openstack-ci-admins and openstack-ci-core with infra-core
|
2014-12-10 20:37:09 +01:00
|
|
|
# 7 - add at least one core team, if no team is defined with special suffixes
|
|
|
|
# like core, admins, milestone or Users
|
2016-02-17 22:39:03 +00:00
|
|
|
# 8 - fix All-Projects inheritance shadowed by exclusiveGroupPermissions
|
2023-03-02 11:17:23 +11:00
|
|
|
# 9 - Ensure submit requirements
|
|
|
|
# * functions only noblock
|
|
|
|
# * each label has a s-r block
|
2014-05-09 14:53:14 +00:00
|
|
|
|
|
|
|
import re
|
|
|
|
import sys
|
|
|
|
|
|
|
|
aclfile = sys.argv[1]
|
|
|
|
|
|
|
|
try:
|
|
|
|
transformations = sys.argv[2:]
|
2014-12-10 20:37:09 +01:00
|
|
|
if transformations and transformations[0] == 'all':
|
2023-03-02 11:17:23 +11:00
|
|
|
transformations = [str(x) for x in range(0, 10)]
|
2014-05-09 14:53:14 +00:00
|
|
|
except KeyError:
|
|
|
|
transformations = []
|
|
|
|
|
|
|
|
|
|
|
|
def tokens(data):
|
|
|
|
"""Human-order comparison
|
|
|
|
|
|
|
|
This handles embedded positive and negative integers, for sorting
|
|
|
|
strings in a more human-friendly order."""
|
|
|
|
data = data.replace('.', ' ').split()
|
|
|
|
for n in range(len(data)):
|
|
|
|
try:
|
|
|
|
data[n] = int(data[n])
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
2023-04-03 09:23:17 -07:00
|
|
|
def normalize_boolean_ops(key, value):
|
|
|
|
# Gerrit 3.6 takes lower-case "and/or" literally -- as in
|
|
|
|
# you literally need to have and/or in the commit string.
|
|
|
|
# Gerrit 3.7 fixes this, but let's standarise on capital
|
|
|
|
# booleans
|
|
|
|
if key in ('copyCondition', 'submittableIf', 'applicableIf'):
|
|
|
|
value = value.replace(' and ', ' AND ')
|
|
|
|
value = value.replace(' or ', ' OR ')
|
|
|
|
return "%s = %s" % (key, value)
|
|
|
|
|
|
|
|
|
2014-05-09 14:53:14 +00:00
|
|
|
acl = {}
|
|
|
|
out = ''
|
|
|
|
|
2016-07-07 08:43:49 +02:00
|
|
|
valid_keys = {'abandon',
|
|
|
|
'access',
|
2023-03-02 09:25:15 +11:00
|
|
|
'applicableIf',
|
2016-07-07 08:43:49 +02:00
|
|
|
'create',
|
2022-01-25 15:15:52 +00:00
|
|
|
'createSignedTag',
|
gerrit/acl : remove deprecated copy conditions
The copy conditions here have been replaced by the "copyCondition"
query tag. This updates the deprecated values to a new query which
does the same thing -- i.e. this should be a noop.
Mostly these are setup to have votes on labels that should be copied
on a no code change/trivial rebase, and if they're -2/+2 (i.e. max
votes are sticky). To be exact the group of
copyallScoresIfNoCodeChange = true
copyAllScoresOnTrivialRebase = true
copyMaxScore = true
copyMinScore = true
becomes
changekind:NO_CODE_CHANGE or changekind:TRIVIAL_REBASE \
or is:MAX or is:MIN
Note all but ocatvia.conf, octavia-dashboard, octavia-lib, and
python-octaviaclient are copying -2/+2 votes; I feel like this is
probably a bug but I have modified these 4 projects to maintain the
same behaviour of not copying the votes.
A small number of projects copy any vote; glance.config,
kayobe.config, kolla.config, nova-specs.config, nova.config,
os-vif.config, placement.config, python-novaclient.config -- they are
replaced with is:ANY.
The old conditions have been deprecated since gerrit 3.5 [1].
Although the old conditions have not been removed yet, this will help
as we think about also changing these to submission requirements for
Gerrit 3.7.
[1] https://gerrit.googlesource.com/gerrit/+/c429ff33d944272b1f4da9f84f904f6403919ea3
Change-Id: Id13fdf588d07c1fec73978e7a69f1d9097989696
2022-12-16 14:31:41 +11:00
|
|
|
'copyCondition',
|
2016-07-07 08:43:49 +02:00
|
|
|
'defaultValue',
|
2021-04-13 18:28:48 +00:00
|
|
|
'delete',
|
2023-03-02 09:25:15 +11:00
|
|
|
'description',
|
2021-01-26 10:23:09 +01:00
|
|
|
'editHashtags',
|
2016-07-07 08:43:49 +02:00
|
|
|
'exclusiveGroupPermissions',
|
|
|
|
'forgeAuthor',
|
|
|
|
'forgeCommitter',
|
|
|
|
'function',
|
2021-04-16 16:04:19 +00:00
|
|
|
'inheritFrom',
|
2022-09-30 13:25:18 +02:00
|
|
|
'label-Allow-Post-Review',
|
2019-05-07 10:54:06 -07:00
|
|
|
'label-Backport-Candidate',
|
2016-07-07 08:43:49 +02:00
|
|
|
'label-Code-Review',
|
2020-02-05 16:25:29 +01:00
|
|
|
'label-PTL-Approved',
|
2016-03-21 12:08:57 +00:00
|
|
|
'label-Review-Priority',
|
2016-07-07 08:43:49 +02:00
|
|
|
'label-Rollcall-Vote',
|
|
|
|
'label-Workflow',
|
|
|
|
'label-Verified',
|
|
|
|
'mergeContent',
|
|
|
|
'push',
|
|
|
|
'pushMerge',
|
|
|
|
'requireChangeId',
|
|
|
|
'requireContributorAgreement',
|
|
|
|
'state',
|
2020-06-23 18:21:30 -04:00
|
|
|
'submit',
|
2023-03-02 09:25:15 +11:00
|
|
|
'submittableIf',
|
2020-12-07 15:41:06 +00:00
|
|
|
'toggleWipState',
|
2017-10-21 18:00:13 +02:00
|
|
|
'value'}
|
2016-07-07 08:43:49 +02:00
|
|
|
|
2014-05-09 14:53:14 +00:00
|
|
|
if '0' in transformations or not transformations:
|
|
|
|
dry_run = True
|
|
|
|
else:
|
|
|
|
dry_run = False
|
|
|
|
|
|
|
|
aclfd = open(aclfile)
|
|
|
|
for line in aclfd:
|
|
|
|
# condense whitespace to single spaces and get rid of leading/trailing
|
2020-01-14 09:39:30 +11:00
|
|
|
line = re.sub(r'\s+', ' ', line).strip()
|
2014-05-09 14:53:14 +00:00
|
|
|
# skip empty lines
|
|
|
|
if not line:
|
|
|
|
continue
|
|
|
|
# this is a section heading
|
|
|
|
if line.startswith('['):
|
|
|
|
section = line.strip(' []')
|
|
|
|
# use a list for this because some options can have the same "key"
|
|
|
|
acl[section] = []
|
|
|
|
# key=value lines
|
|
|
|
elif '=' in line:
|
|
|
|
acl[section].append(line)
|
2016-07-07 08:43:49 +02:00
|
|
|
# Check for valid keys
|
|
|
|
key = line.split('=')[0].strip()
|
|
|
|
if key not in valid_keys:
|
gerrit/acl : remove deprecated copy conditions
The copy conditions here have been replaced by the "copyCondition"
query tag. This updates the deprecated values to a new query which
does the same thing -- i.e. this should be a noop.
Mostly these are setup to have votes on labels that should be copied
on a no code change/trivial rebase, and if they're -2/+2 (i.e. max
votes are sticky). To be exact the group of
copyallScoresIfNoCodeChange = true
copyAllScoresOnTrivialRebase = true
copyMaxScore = true
copyMinScore = true
becomes
changekind:NO_CODE_CHANGE or changekind:TRIVIAL_REBASE \
or is:MAX or is:MIN
Note all but ocatvia.conf, octavia-dashboard, octavia-lib, and
python-octaviaclient are copying -2/+2 votes; I feel like this is
probably a bug but I have modified these 4 projects to maintain the
same behaviour of not copying the votes.
A small number of projects copy any vote; glance.config,
kayobe.config, kolla.config, nova-specs.config, nova.config,
os-vif.config, placement.config, python-novaclient.config -- they are
replaced with is:ANY.
The old conditions have been deprecated since gerrit 3.5 [1].
Although the old conditions have not been removed yet, this will help
as we think about also changing these to submission requirements for
Gerrit 3.7.
[1] https://gerrit.googlesource.com/gerrit/+/c429ff33d944272b1f4da9f84f904f6403919ea3
Change-Id: Id13fdf588d07c1fec73978e7a69f1d9097989696
2022-12-16 14:31:41 +11:00
|
|
|
raise Exception('(%s) Unrecognized key "%s" in line: "%s"'
|
|
|
|
% (aclfile, key, line))
|
2014-05-09 14:53:14 +00:00
|
|
|
# WTF
|
|
|
|
else:
|
2015-03-31 14:15:26 -07:00
|
|
|
raise Exception('Unrecognized line: "%s"' % line)
|
2014-05-09 14:53:14 +00:00
|
|
|
aclfd.close()
|
|
|
|
|
|
|
|
if '2' in transformations:
|
2015-04-01 10:40:26 -07:00
|
|
|
for key in acl:
|
|
|
|
if key.startswith('access "refs/tags/'):
|
|
|
|
acl[key] = [
|
|
|
|
x for x in acl[key]
|
|
|
|
if not x.startswith('create = ')]
|
2014-05-09 14:53:14 +00:00
|
|
|
|
|
|
|
if '3' in transformations:
|
|
|
|
try:
|
|
|
|
acl['project'] = [x for x in acl['project'] if x not in
|
|
|
|
('state = active', 'status = active')]
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
if '4' in transformations:
|
|
|
|
for section in acl.keys():
|
2020-01-14 09:39:30 +11:00
|
|
|
acl[section] = [x for x in acl[section]
|
|
|
|
if x != 'owner = group Administrators']
|
2014-05-09 14:53:14 +00:00
|
|
|
|
|
|
|
if '5' in transformations:
|
|
|
|
for section in acl.keys():
|
|
|
|
newsection = []
|
|
|
|
for option in acl[section]:
|
2023-03-02 11:09:40 +11:00
|
|
|
key, value = [x.strip() for x in option.split('=', 1)]
|
2014-05-09 14:53:14 +00:00
|
|
|
if key == 'exclusiveGroupPermissions':
|
|
|
|
newsection.append('%s = %s' % (
|
|
|
|
key, ' '.join(sorted(value.split()))))
|
|
|
|
else:
|
|
|
|
newsection.append(option)
|
|
|
|
acl[section] = newsection
|
|
|
|
|
2014-06-30 17:30:45 +00:00
|
|
|
if '6' in transformations:
|
|
|
|
for section in acl.keys():
|
|
|
|
newsection = []
|
|
|
|
for option in acl[section]:
|
|
|
|
for group in ('openstack-ci-admins', 'openstack-ci-core'):
|
|
|
|
option = option.replace('group %s' % group, 'group infra-core')
|
|
|
|
newsection.append(option)
|
|
|
|
acl[section] = newsection
|
|
|
|
|
2014-12-10 20:37:09 +01:00
|
|
|
if '7' in transformations:
|
2014-12-16 13:43:26 -08:00
|
|
|
special_projects = (
|
|
|
|
'ossa',
|
2014-12-19 11:34:50 -08:00
|
|
|
'reviewday',
|
2014-12-16 13:43:26 -08:00
|
|
|
)
|
|
|
|
special_teams = (
|
|
|
|
'admins',
|
2015-04-24 01:06:04 +00:00
|
|
|
'Bootstrappers',
|
2014-12-16 13:43:26 -08:00
|
|
|
'committee',
|
|
|
|
'core',
|
|
|
|
'maint',
|
|
|
|
'Managers',
|
|
|
|
'milestone',
|
2014-12-19 11:34:50 -08:00
|
|
|
'packagers',
|
2015-04-21 21:22:35 +00:00
|
|
|
'release',
|
2014-12-16 13:43:26 -08:00
|
|
|
'Users',
|
|
|
|
)
|
2014-12-10 20:37:09 +01:00
|
|
|
for section in acl.keys():
|
|
|
|
newsection = []
|
|
|
|
for option in acl[section]:
|
2014-12-16 13:43:26 -08:00
|
|
|
if ('refs/heads' in section and 'group' in option
|
|
|
|
and '-2..+2' in option
|
|
|
|
and not any(x in option for x in special_teams)
|
|
|
|
and not any(x in aclfile for x in special_projects)):
|
|
|
|
option = '%s%s' % (option, '-core')
|
2014-12-10 20:37:09 +01:00
|
|
|
newsection.append(option)
|
|
|
|
acl[section] = newsection
|
|
|
|
|
2016-02-17 22:39:03 +00:00
|
|
|
if '8' in transformations:
|
|
|
|
for section in acl.keys():
|
|
|
|
newsection = []
|
|
|
|
for option in acl[section]:
|
|
|
|
newsection.append(option)
|
2023-03-02 11:09:40 +11:00
|
|
|
key, value = [x.strip() for x in option.split('=', 1)]
|
2016-02-17 22:39:03 +00:00
|
|
|
if key == 'exclusiveGroupPermissions':
|
|
|
|
exclusives = value.split()
|
|
|
|
# It's safe for these to be duplicates since we de-dup later
|
|
|
|
if 'abandon' in exclusives:
|
|
|
|
newsection.append('abandon = group Change Owner')
|
|
|
|
newsection.append('abandon = group Project Bootstrappers')
|
|
|
|
if 'label-Code-Review' in exclusives:
|
|
|
|
newsection.append('label-Code-Review = -2..+2 '
|
|
|
|
'group Project Bootstrappers')
|
|
|
|
newsection.append('label-Code-Review = -1..+1 '
|
|
|
|
'group Registered Users')
|
|
|
|
if 'label-Workflow' in exclusives:
|
|
|
|
newsection.append('label-Workflow = -1..+1 '
|
|
|
|
'group Project Bootstrappers')
|
|
|
|
newsection.append('label-Workflow = -1..+0 '
|
|
|
|
'group Change Owner')
|
|
|
|
acl[section] = newsection
|
|
|
|
|
2023-03-02 11:17:23 +11:00
|
|
|
# submit-requirements have taken over the role of "function" in labels
|
|
|
|
# since Gerrit 3.6. We ensure that the only function in a label
|
|
|
|
# section now is the noop "NoBlock" function -- all labels now need to
|
|
|
|
# explicitly write their own submit-requirement. e.g. for any
|
|
|
|
# [label "Foo"]
|
|
|
|
# there should be a matching submit requirement section
|
|
|
|
# [submit-requirement "Foo"]
|
|
|
|
# We can't really decide what the rules will be, so we just add the
|
|
|
|
# section with a dummy comment.
|
|
|
|
if '9' in transformations:
|
|
|
|
missing_sr = {}
|
|
|
|
for section in acl.keys():
|
2023-04-03 09:23:17 -07:00
|
|
|
newsection = []
|
2023-03-02 11:17:23 +11:00
|
|
|
if section.startswith("label "):
|
|
|
|
label_name = section.split(' ')[1]
|
|
|
|
sr_found = False
|
|
|
|
for sr in acl.keys():
|
|
|
|
if sr == 'submit-requirement %s' % (label_name):
|
|
|
|
sr_found = True
|
|
|
|
break
|
|
|
|
if not sr_found:
|
|
|
|
msg = ('# You must have a submit-requirement section for %s'
|
|
|
|
% label_name)
|
|
|
|
missing_sr['submit-requirement %s' % label_name] = [msg]
|
|
|
|
|
2023-04-03 09:23:17 -07:00
|
|
|
keys = []
|
|
|
|
for option in acl[section]:
|
|
|
|
key, value = [x.strip() for x in option.split('=', 1)]
|
|
|
|
keys.append(key)
|
|
|
|
# Insert an inline comment if the ACL uses an invalid function
|
|
|
|
if key == 'function':
|
|
|
|
if value != 'NoBlock':
|
|
|
|
newsection.append(
|
|
|
|
'# XXX: The only supported function type is '
|
|
|
|
'NoBlock')
|
|
|
|
newsection.append(normalize_boolean_ops(key, value))
|
|
|
|
# Add function = NoBlock to label sections if not set as the
|
|
|
|
# default is MaxWithBlock which will interfere with submit
|
|
|
|
# requirements.
|
|
|
|
if 'function' not in keys:
|
|
|
|
newsection.append('function = NoBlock')
|
|
|
|
else:
|
|
|
|
for option in acl[section]:
|
|
|
|
key, value = [x.strip() for x in option.split('=', 1)]
|
|
|
|
newsection.append(normalize_boolean_ops(key, value))
|
2023-03-16 13:36:26 +11:00
|
|
|
|
2023-03-02 11:17:23 +11:00
|
|
|
acl[section] = newsection
|
|
|
|
acl.update(missing_sr)
|
|
|
|
|
2014-05-09 14:53:14 +00:00
|
|
|
for section in sorted(acl.keys()):
|
|
|
|
if acl[section]:
|
|
|
|
out += '\n[%s]\n' % section
|
2016-02-17 22:31:54 +00:00
|
|
|
lastoption = ''
|
2014-05-09 14:53:14 +00:00
|
|
|
for option in sorted(acl[section], key=tokens):
|
2016-02-17 22:31:54 +00:00
|
|
|
if option != lastoption:
|
2023-04-07 17:07:51 +00:00
|
|
|
# Gerrit prefers all option lines indented by a single
|
|
|
|
# hard tab; this minimises diffs if things like
|
|
|
|
# upgrades need to modify the acls
|
|
|
|
out += '\t%s\n' % option
|
2016-02-17 22:31:54 +00:00
|
|
|
lastoption = option
|
2014-05-09 14:53:14 +00:00
|
|
|
|
|
|
|
if dry_run:
|
|
|
|
print(out[1:-1])
|
|
|
|
else:
|
|
|
|
aclfd = open(aclfile, 'w')
|
|
|
|
aclfd.write(out[1:])
|
|
|
|
aclfd.close()
|