diff --git a/doc/source/_exts/deliverables.py b/doc/source/_exts/deliverables.py index 7e998ac4fc..cb3cabb300 100644 --- a/doc/source/_exts/deliverables.py +++ b/doc/source/_exts/deliverables.py @@ -21,17 +21,17 @@ from docutils import nodes from docutils.parsers import rst from docutils.parsers.rst import directives from docutils.statemachine import ViewList +from openstack_governance import governance from sphinx.util import logging from sphinx.util.nodes import nested_parse_with_titles from openstack_releases import deliverable -from openstack_releases import governance from openstack_releases import links from openstack_releases import series_status LOG = logging.getLogger(__name__) -_TEAM_DATA = governance.get_team_data() +_GOV_DATA = governance.Governance.from_remote_repo() _PHASE_DOC_URL = 'https://docs.openstack.org/project-team-guide/stable-branches.html#maintenance-phases' # noqa @@ -443,14 +443,14 @@ class HighlightsDirective(rst.Directive): LOG.info('[highlights] rendering %s highlights for %s', team.title(), series) - tdata = _TEAM_DATA.get(team, {}) + tdata = _GOV_DATA.get_team(team) title = team.title() - if tdata.get('service'): - title = "{} - {}".format(title, tdata['service']) + if tdata.service: + title = "{} - {}".format(title, tdata.service) result.append(title, source_name) result.append('-' * len(title), source_name) - if tdata.get('mission'): - result.append(tdata['mission'], source_name) + if tdata.mission: + result.append(tdata.mission, source_name) result.append('', source_name) result.append('**Notes:**', source_name) result.append('', source_name) diff --git a/openstack_releases/cmds/find_gerrit_acl_issues.py b/openstack_releases/cmds/find_gerrit_acl_issues.py index bcc5e58915..d575b2a278 100644 --- a/openstack_releases/cmds/find_gerrit_acl_issues.py +++ b/openstack_releases/cmds/find_gerrit_acl_issues.py @@ -23,13 +23,13 @@ import os.path import urllib import appdirs +from openstack_governance import governance import requests from requests.packages import urllib3 import openstack_releases from openstack_releases import defaults from openstack_releases import deliverable -from openstack_releases import governance # Disable warnings about insecure connections. urllib3.disable_warnings() @@ -121,7 +121,7 @@ def main(): if not config.has_option('DEFAULT', 'password'): parser.error('No password set in {}'.format(config_filename)) - team_data = governance.get_team_data() + gov_data = governance.Governance.from_remote_repo() # Some deliverables were independent at one time but might not be # any more, so compare the independent list with the current @@ -148,7 +148,12 @@ def main(): config['DEFAULT']['password'], ) - for repo in governance.get_repositories(team_data, code_only=True): + for repo in gov_data.get_repositories(): + + if repo.name.endswith('-specs'): + continue + if 'cookiecutter' in repo.name: + continue if repo.deliverable.team.name in IGNORED_TEAMS: if args.verbose: diff --git a/openstack_releases/cmds/list_changes.py b/openstack_releases/cmds/list_changes.py index a41a0e654f..9662313b49 100644 --- a/openstack_releases/cmds/list_changes.py +++ b/openstack_releases/cmds/list_changes.py @@ -29,15 +29,16 @@ import subprocess import sys import tempfile +from openstack_governance import governance import pyfiglet import requests from openstack_releases import defaults from openstack_releases import deliverable from openstack_releases import gitutils -from openstack_releases import governance from openstack_releases import hound from openstack_releases import release_notes +from openstack_releases import wiki from openstack_releases import yamlutils @@ -241,10 +242,10 @@ def main(): print('not cleaning up %s' % workdir) atexit.register(cleanup_workdir) - team_data = governance.get_team_data() + gov_data = governance.Governance.from_remote_repo() official_repos = set( r.name - for r in governance.get_repositories(team_data) + for r in gov_data.get_repositories() ) all_deliverables = deliverable.Deliverables( @@ -252,6 +253,8 @@ def main(): False, ) + liaison_data = wiki.get_liaison_data() + # Remove any inherited PAGER environment variable to avoid # blocking the output waiting for input. os.environ['PAGER'] = '' @@ -273,12 +276,16 @@ def main(): header('Team details') if deliv.team: team_name = deliv.team - team_dict = team_data.get(team_name) - if team_dict: - team = governance.Team(team_name, team_dict) + try: + team = gov_data.get_team(team_name) + except ValueError: + team = None + if team: print('found team %s' % team_name) print(' PTL : %(name)s (%(irc)s)' % team.ptl) - print(' Liaison: %s (%s)\n' % team.liaison) + team_liaison = liaison_data.get(team.name.lower(), {}) + print(' Liaison: %(Liaison)s (%(IRC Handle)s)\n' % + team_liaison) team_deliv = team.deliverables.get(deliv.name) if team_deliv: print('found deliverable %s' % deliv.name) diff --git a/openstack_releases/cmds/validate.py b/openstack_releases/cmds/validate.py index 0d7d2e3532..515f3cc935 100644 --- a/openstack_releases/cmds/validate.py +++ b/openstack_releases/cmds/validate.py @@ -32,6 +32,7 @@ import shutil import sys import tempfile +from openstack_governance import governance import requests import six @@ -41,7 +42,6 @@ from requests.packages import urllib3 from openstack_releases import defaults from openstack_releases import deliverable from openstack_releases import gitutils -from openstack_releases import governance from openstack_releases import npmutils from openstack_releases import project_config from openstack_releases import puppetutils @@ -470,7 +470,9 @@ def validate_bugtracker(deliv, context): def validate_team(deliv, context): "Look for the team name in the governance data." - if deliv.team not in context.team_data: + try: + context.gov_data.get_team(deliv.team) + except ValueError: context.warning( 'Team {} not in governance data. ' 'Only official teams should use this repository ' @@ -1309,8 +1311,7 @@ def validate_new_releases(deliv, context): final_release = deliv.releases[-1] expected_repos = set( r.name - for r in governance.get_repositories( - context.team_data, + for r in context.gov_data.get_repositories( deliverable_name=deliv.name, ) ) @@ -1633,7 +1634,7 @@ def validate_branch_points(deliv, context): class ValidationContext(object): _zuul_projects = None - _team_data = None + _gov_data = None def __init__(self, debug=False, cleanup=True): self.warnings = [] @@ -1693,10 +1694,10 @@ class ValidationContext(object): return self._zuul_projects @property - def team_data(self): - if not self._team_data: - self._team_data = governance.get_team_data() - return self._team_data + def gov_data(self): + if not self._gov_data: + self._gov_data = governance.Governance.from_remote_repo() + return self._gov_data def main(): diff --git a/openstack_releases/deliverable.py b/openstack_releases/deliverable.py index 6b0ad76cf6..9af64c951a 100644 --- a/openstack_releases/deliverable.py +++ b/openstack_releases/deliverable.py @@ -22,9 +22,9 @@ import os import os.path import weakref +from openstack_governance import governance import pbr.version -from openstack_releases import governance from openstack_releases import series_status from openstack_releases import yamlutils @@ -369,7 +369,7 @@ class Branch(object): @functools.total_ordering class Deliverable(object): - _governance_data = None + _gov_data = None _series_status_data = None def __init__(self, team, series, name, data): @@ -554,10 +554,9 @@ class Deliverable(object): @property def tags(self): - if self._governance_data is None: - Deliverable._governance_data = governance.get_team_data() - return governance.get_tags_for_deliverable( - self._governance_data, self.team, self.name) + if self._gov_data is None: + Deliverable._gov_data = governance.Governance.from_remote_repo() + return self._gov_data.get_team(self.team).deliverables[self.name].tags @property def filename(self): diff --git a/openstack_releases/governance.py b/openstack_releases/governance.py deleted file mode 100644 index 999f640917..0000000000 --- a/openstack_releases/governance.py +++ /dev/null @@ -1,178 +0,0 @@ -# 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. - -"""Work with the governance repository. -""" - -import weakref - -from openstack_releases import wiki -from openstack_releases import yamlutils - -import requests - -PROJECTS_LIST = "http://git.openstack.org/cgit/openstack/governance/plain/reference/projects.yaml" # noqa - - -def get_team_data(url=PROJECTS_LIST): - """Return the parsed team data from the governance repository. - - :param url: Optional URL to the location of the projects.yaml - file. Defaults to the most current version in the public git - repository. - - """ - r = requests.get(url) - return yamlutils.loads(r.text) - - -def get_tags_for_deliverable(team_data, team, name): - "Return the tags for the deliverable owned by the team." - if team not in team_data: - return set() - team_info = team_data[team] - dinfo = team_info['deliverables'].get(name) - if not dinfo: - return set() - return set(dinfo.get('tags', [])).union(set(team_info.get('tags', []))) - - -def get_repo_owner(team_data, repo_name): - """Return the name of the team that owns the repository. - - :param team_data: The result of calling :func:`get_team_data` - :param repo_name: Long name of the repository, such as 'openstack/nova'. - - """ - for team, info in team_data.items(): - for dname, dinfo in info.get('deliverables', {}).items(): - if repo_name in dinfo.get('repos', []): - return team - raise ValueError('Repository %s not found in governance list' % repo_name) - - -class Team(object): - _liaison_data = None - - def __init__(self, name, data): - self.name = name - self.data = data - # Protectively initialize the ptl data structure in case part - # of it is missing from the project list, then replace any - # values we do have from that data. - self.ptl = { - 'name': 'MISSING', - 'irc': 'MISSING', - } - self.ptl.update(data.get('ptl', {})) - self.deliverables = { - dn: Deliverable(dn, di, self) - for dn, di in self.data.get('deliverables', {}).items() - } - - @property - def tags(self): - return set(self.data.get('tags', [])) - - @property - def liaison(self): - if self._liaison_data is None: - # Only hit the wiki page one time. - Team._liaison_data = wiki.get_liaison_data() - team_liaison = self._liaison_data.get(self.name.lower(), {}) - return (team_liaison.get('Liaison'), - team_liaison.get('IRC Handle')) - - -class Deliverable(object): - def __init__(self, name, data, team): - self.name = name - self.data = data - self.team = weakref.proxy(team) - self.repositories = { - rn: Repository(rn, self) - for rn in self.data.get('repos', []) - } - - @property - def type(self): - for t in self.tags: - if t.startswith('type:'): - return t.partition(':')[-1] - return 'other' - - @property - def model(self): - for t in self.tags: - if t.startswith('release:'): - return t.partition(':')[-1] - return 'none' - - @property - def tags(self): - return set(self.data.get('tags', [])).union(self.team.tags) - - -class Repository(object): - def __init__(self, name, deliverable): - self.name = name - self.deliverable = weakref.proxy(deliverable) - - @property - def tags(self): - return self.deliverable.tags - - @property - def code_related(self): - return not (self.name.endswith('-specs') or - 'cookiecutter' in self.name) - - -def get_repositories(team_data, team_name=None, deliverable_name=None, - tags=[], code_only=False): - """Return a sequence of repositories, possibly filtered. - - :param team_data: The result of calling :func:`get_team_data` - :param team_name: The name of the team owning the repositories. Can be - None. - :para deliverable_name: The name of the deliverable to which all - repos should belong. - :param tags: The names of any tags the repositories should - have. Can be empty. - :param code_only: Boolean indicating whether to return only code - repositories (ignoring specs and cookiecutter templates). - - """ - if tags: - tags = set(tags) - if team_name: - try: - teams = [Team(team_name, team_data[team_name])] - except KeyError: - raise RuntimeError('No team %r found in %r' % - (team_name, list(team_data.keys()))) - else: - teams = [Team(n, i) for n, i in team_data.items()] - for team in teams: - if deliverable_name and deliverable_name not in team.deliverables: - continue - if deliverable_name: - deliverables = [team.deliverables[deliverable_name]] - else: - deliverables = team.deliverables.values() - for deliverable in deliverables: - for repository in deliverable.repositories.values(): - if tags and not tags.issubset(repository.tags): - continue - if code_only and not repository.code_related: - continue - yield repository diff --git a/openstack_releases/tests/test_governance.py b/openstack_releases/tests/test_governance.py deleted file mode 100644 index 49f84cbe7b..0000000000 --- a/openstack_releases/tests/test_governance.py +++ /dev/null @@ -1,141 +0,0 @@ -# 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. - -from oslotest import base - -from openstack_releases import governance -from openstack_releases import yamlutils - - -_team_data_yaml = """ -Release Management: - ptl: - name: Doug Hellmann - irc: dhellmann - email: doug@doughellmann.com - irc-channel: openstack-release - mission: > - Coordinating the release of OpenStack deliverables, by defining the - overall development cycle, release models, publication processes, - versioning rules and tools, then enabling project teams to produce - their own releases. - url: https://wiki.openstack.org/wiki/Release_Management - tags: - - team:diverse-affiliation - deliverables: - release-schedule-generator: - repos: - - openstack/release-schedule-generator - release-test: - repos: - - openstack/release-test - release-tools: - repos: - - openstack-infra/release-tools - releases: - repos: - - openstack/releases - reno: - repos: - - openstack/reno - docs: - contributor: https://docs.openstack.org/developer/reno/ - specs-cookiecutter: - repos: - - openstack-dev/specs-cookiecutter -""" - -TEAM_DATA = yamlutils.loads(_team_data_yaml) - - -class TestGetRepoOwner(base.BaseTestCase): - - def test_repo_exists(self): - owner = governance.get_repo_owner( - TEAM_DATA, - 'openstack/releases', - ) - self.assertEqual('Release Management', owner) - - def test_no_such_repo(self): - self.assertRaises( - ValueError, - governance.get_repo_owner, - TEAM_DATA, - 'openstack/no-such-repo', - ) - - -class TestGetRepositories(base.BaseTestCase): - - def test_by_team(self): - repos = governance.get_repositories( - TEAM_DATA, - team_name='Release Management', - ) - self.assertEqual( - sorted(['openstack/release-schedule-generator', - 'openstack/release-test', - 'openstack-infra/release-tools', - 'openstack/releases', - 'openstack/reno', - 'openstack-dev/specs-cookiecutter']), - sorted(r.name for r in repos), - ) - - def test_by_deliverable(self): - repos = governance.get_repositories( - TEAM_DATA, - deliverable_name='release-tools', - ) - self.assertEqual( - ['openstack-infra/release-tools'], - [r.name for r in repos], - ) - - def test_code_only(self): - repos = governance.get_repositories( - TEAM_DATA, - code_only=True, - ) - self.assertEqual( - sorted(['openstack/release-schedule-generator', - 'openstack/release-test', - 'openstack-infra/release-tools', - 'openstack/releases', - 'openstack/reno']), - sorted(r.name for r in repos), - ) - - def test_tag_negative(self): - repos = governance.get_repositories( - TEAM_DATA, - tags=['team:single-vendor'], - ) - self.assertEqual([], list(repos)) - - def test_tags_positive(self): - repos = governance.get_repositories( - TEAM_DATA, - tags=['team:diverse-affiliation'], - ) - self.assertEqual( - sorted(['openstack/release-schedule-generator', - 'openstack/release-test', - 'openstack-infra/release-tools', - 'openstack/releases', - 'openstack/reno', - 'openstack-dev/specs-cookiecutter']), - sorted(r.name for r in repos), - ) diff --git a/requirements.txt b/requirements.txt index 74334350ec..0a36fa88aa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,3 +32,5 @@ pyfiglet>=0.7.5 appdirs packaging>=16.5 + +openstack-governance>=0.1.0 # Apache 2.0