move aclmanager.py from release-tools repo

The aclmanager script needs to be able to get a list of repositories
with the cycle-with-milestone release model, and the data for that lives
in this repository now instead of the governance repository. Rather than
having a tool that relies on this data live in release-tools, move it
here.

Change-Id: I7c2c54c4949829aea92e569e3ac9ec9007d932ed
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
This commit is contained in:
Doug Hellmann 2017-01-09 09:40:59 -05:00
parent 406de70380
commit c22f58ae3b
3 changed files with 252 additions and 4 deletions

View File

@ -78,14 +78,19 @@ Between Milestone-2 and Milestone-3
1. Update ACLs for refs/heads/stable/$series so that members of 1. Update ACLs for refs/heads/stable/$series so that members of
$project-release-branch can approve changes. The patch can be $project-release-branch can approve changes. The patch can be
generated (for all release:cycle-with-milestones deliverables) with: generated (for all release:cycle-with-milestones deliverables)
``aclmanager.py acls /path/to/openstack-infra/project-config $series`` with::
tox -e venv -- python ./tools/aclmanager.py acls /path/to/openstack-infra/project-config $series
2. Set the population of all $project-release-branch groups to the 2. Set the population of all $project-release-branch groups to the
"Release Managers" group and $project-release. This can be done "Release Managers" group and $project-release. This can be done
(for all release:cycle-with-milestones deliverables) by running (for all release:cycle-with-milestones deliverables) by running
``aclmanager.py groups pre_release $user`` ($user being your Gerrit ``aclmanager.py``::
username)
tox -e venv -- python ./tools/aclmanager.py groups pre_release $user
($user being your Gerrit username)
3. Ask the release liaisons for the affected teams to update the 3. Ask the release liaisons for the affected teams to update the
contents of their $project-release groups. For new projects in contents of their $project-release groups. For new projects in

View File

@ -459,3 +459,34 @@ easy as ``pip install .`` in this repository directory.
milestone-based projects at the end of the cycle milestone-based projects at the end of the cycle
* ``init-series`` initializes a new deliverable directory with stub * ``init-series`` initializes a new deliverable directory with stub
files based on the previous release. files based on the previous release.
tools/aclmanager.py
-------------------
A script to handle pre-release/post-release ACLs on stable/$SERIES
branches.
The 'acls' action helps to produce a patch over
openstack-infra/project-config that inserts a specific ACL for
stable/$SERIES.
The 'groups' action helps to adjust the membership of
$PROJ-release-branch Gerrit group, based on which stage the release
branch is at. At pre-release we remove $PROJ-stable-maint, and add the
$PROJ-release and Release Managers group (pre_release subaction). At
post-release, we remove $PROJ-release and Release Managers, and add
$PROJ-stable-maint (post_release subaction).
Examples:
To create the ACL patch for stable/newton:
::
tox -e venv -- python ./tools/aclmanager.py acls ~/branches/openstack-infra/project-config newton
To set the pre-release group membership:
::
tox -e venv -- python ./tools/aclmanager.py groups pre_release ttx

212
tools/aclmanager.py Executable file
View File

@ -0,0 +1,212 @@
#!/usr/bin/python
#
# Handle pre-release / post-release ACLs for milestone-driven projects
#
# Copyright 2016 Thierry Carrez <thierry@openstack.org>
# All Rights Reserved.
#
# 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 getpass
import os
import sys
import requests
import yaml
import openstack_releases
from openstack_releases import deliverable
GERRIT_URL = 'https://review.openstack.org/'
EXCEPTIONS = ['openstack/training-labs',
'openstack/murano-apps',
'openstack/trove-image-builder']
def repositories_list(deliverables_dir, series):
"""Yields (team, repo) tuples for cycle-with-milestones deliverables"""
deliverables = deliverable.Deliverables(deliverables_dir)
for team, series, dname, dinfo in deliverables.get_deliverables(None, series):
d = deliverable.Deliverable(team, series, dname, dinfo)
if d.model != 'cycle-with-milestones':
continue
for repo in sorted(d.repos):
yield (d.team, repo)
def patch_acls(args):
"""Handles the acls action"""
blob = """[access "refs/heads/stable/{branch}"]
abandon = group Change Owner
abandon = group Project Bootstrappers
abandon = group {group}
exclusiveGroupPermissions = abandon label-Code-Review label-Workflow
label-Code-Review = -2..+2 group Project Bootstrappers
label-Code-Review = -2..+2 group {group}
label-Code-Review = -1..+1 group Registered Users
label-Workflow = -1..+0 group Change Owner
label-Workflow = -1..+1 group Project Bootstrappers
label-Workflow = -1..+1 group {group}
"""
# Load repo/aclfile mapping from Gerrit config
projectsyaml = os.path.join(args.repository, 'gerrit', 'projects.yaml')
acl = {}
config = yaml.load(open(projectsyaml))
for project in config:
aclfilename = project.get('acl-config')
if aclfilename:
(head, tail) = os.path.split(aclfilename)
acl[project['project']] = os.path.join(os.path.basename(head),
tail)
else:
acl[project['project']] = project['project'] + '.config'
# Get the list of ACL files to update
aclfiles = {}
for team, repo in repositories_list(args.deliverables_dir, args.series):
try:
aclfiles[acl[repo]] = team
except KeyError:
print('No ACL file defined for %s' % repo)
sys.exit(1)
for aclfn, teamname in aclfiles.items():
newcontent = ''
fullfilename = os.path.join(args.repository, 'gerrit', 'acls', aclfn)
group = '%s-release-branch' % teamname
print('Patching %s' % fullfilename)
if args.dryrun:
print('Adding stable/%s ACL with rights for %s' %
(args.series, group))
else:
with open(fullfilename) as aclfile:
hit = False
for line in aclfile:
if line.startswith("[receive]"):
newcontent += blob.format(
branch=args.series,
group=group)
hit = True
newcontent += line
if not hit:
print("Could not update %s automatically" % fullfilename)
with open(fullfilename, 'w') as aclfile:
aclfile.write(newcontent)
def gerrit_group_membership_test(gerritauth, action, group, member):
"""Test for Gerrit group membership based on action taken"""
call = 'a/groups/%s/groups/%s' % (group, member)
r = requests.get(GERRIT_URL + call, auth=gerritauth)
if action == 'PUT':
# For PUT operations, return true if member is missing
return r.status_code == 404
else:
# For DELETE operations, return true if member already there
return r.status_code == 200
def modify_gerrit_groups(args):
"""Handles the 'groups' action"""
gerritauth = requests.auth.HTTPDigestAuth(args.username, getpass.getpass())
if args.stage == 'pre_release':
# At pre-release stage we want to have $PROJECT-release and
# Release Managers (and remove $PROJECT-stable-maint if present)
actions = [
('PUT', lambda x: '%s-release' % x),
('PUT', lambda x: 'Release Managers'),
('DELETE', lambda x: '%s-stable-maint' % x),
]
elif args.stage == 'post_release':
# At post-release stage we want to have $PROJECT-stable-maint
# (and remove Release Managers and $PROJECT-release if present)
actions = [
('PUT', lambda x: '%s-stable-maint' % x),
('DELETE', lambda x: 'Release Managers'),
('DELETE', lambda x: '%s-release' % x),
]
# Build the list of calls to make
print('Computing the list of modifications')
calls = set()
for team, _ in repositories_list():
group = '%s-release-branch' % team
for (verb, memberformat) in actions:
member = memberformat(team)
# Filter based on already-handled names
if gerrit_group_membership_test(gerritauth, verb, group, member):
calls.add((verb, group, member))
else:
print("Skipping %s %s in %s (already done)" %
(verb, member, group))
for verb, group, member in calls:
call = 'a/groups/%s/groups/%s' % (group, member)
print('Updating %s group using %s %s' % (group, verb, call))
if not args.dryrun:
r = requests.request(verb, GERRIT_URL + call, auth=gerritauth)
if r.status_code not in (201, 204):
print('Error (%d) while updating group' % r.status_code)
def main(args=sys.argv[1:]):
parser = argparse.ArgumentParser()
parser.add_argument(
'--dryrun',
default=False,
help='do not actually do anything',
action='store_true')
subparsers = parser.add_subparsers(title='commands')
do_acls = subparsers.add_parser(
'acls',
help='patch ACL files')
do_acls.add_argument(
'--deliverables-dir',
default=openstack_releases.deliverable_dir,
help='location of deliverable files',
)
do_acls.add_argument(
'repository',
help='location of the local project-config repository')
do_acls.add_argument(
'series',
help='series to generate ACL for')
do_acls.set_defaults(func=patch_acls)
do_groups = subparsers.add_parser(
'groups',
help='modify Gerrit groups membership')
do_groups.add_argument(
'stage',
choices=['pre_release', 'post_release'],
help='type of modification to push')
do_groups.add_argument(
'username',
help='gerrit HTTP username')
do_groups.set_defaults(func=modify_gerrit_groups)
args = parser.parse_args(args)
if args.dryrun:
print('Running in dry run mode, no action will be actually taken')
return args.func(args)
if __name__ == '__main__':
main()