From de52a1fefa648b942c7fa53319f9e0195d970cd9 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Fri, 20 Apr 2018 11:08:03 -0400 Subject: [PATCH] add a validation rule to block new releases based on series status We agreed at the PTG in Dublin that we would not allow releases from branches in 'extended maintenance' mode, so that leaves 'development' and 'maintenance'. Story: #2001852 Change-Id: I129bdd1c6103615179a7fb7918d37fd02ba0b914 Signed-off-by: Doug Hellmann --- openstack_releases/cmds/validate.py | 46 ++++++++ openstack_releases/deliverable.py | 8 ++ openstack_releases/series_status.py | 10 ++ openstack_releases/tests/test_validate.py | 136 ++++++++++++++++++++++ 4 files changed, 200 insertions(+) diff --git a/openstack_releases/cmds/validate.py b/openstack_releases/cmds/validate.py index c9dbb49076..384a3d0fa7 100644 --- a/openstack_releases/cmds/validate.py +++ b/openstack_releases/cmds/validate.py @@ -875,6 +875,51 @@ def validate_new_releases_at_end(deliv, context): print('OK') +@applies_to_released +def validate_new_releases_in_open_series(deliv, context): + "New releases may only be added to open series." + + if deliv.series_info.allows_releases: + print('{} has status {!r} and allows releases'.format( + deliv.series, deliv.series_info.status)) + return + + LOG.debug('%s has status %r and will not allow releases', + deliv.series, deliv.series_info.status) + + # Remember which entries are new so we can verify that they + # appear at the end of the file. + new_releases = {} + + for release in deliv.releases: + + for project in release.projects: + + if not gitutils.safe_clone_repo(context.workdir, project.repo.name, + project.hash, context): + continue + + version_exists = gitutils.commit_exists( + context.workdir, project.repo.name, release.version, + ) + if version_exists: + print('tag exists, skipping further validation') + continue + + LOG.debug('Found new version {} for {}'.format( + release.version, project.repo)) + new_releases[release.version] = release + + if new_releases: + # The series is closed but there is a new release. + msg = ('series {} has status {!r} ' + 'and cannot have new releases tagged').format( + deliv.series, deliv.series_info.status) + context.error(msg) + else: + print('OK') + + @applies_to_released def validate_release_branch_membership(deliv, context): "Commits being tagged need to be on the right branch." @@ -1471,6 +1516,7 @@ def main(): validate_existing_tags, validate_version_numbers, validate_new_releases_at_end, + validate_new_releases_in_open_series, validate_release_branch_membership, validate_tarball_base, validate_new_releases, diff --git a/openstack_releases/deliverable.py b/openstack_releases/deliverable.py index 5956d686b3..2355bc6f10 100644 --- a/openstack_releases/deliverable.py +++ b/openstack_releases/deliverable.py @@ -25,6 +25,7 @@ import weakref import pbr.version from openstack_releases import governance +from openstack_releases import series_status from openstack_releases import yamlutils @@ -330,6 +331,7 @@ class Branch(object): class Deliverable(object): _governance_data = None + _series_status_data = None def __init__(self, team, series, name, data): self.team = team @@ -522,6 +524,12 @@ class Deliverable(object): def cycle_highlights(self): return self._data.get('cycle-highlights', []) + @property + def series_info(self): + if self._series_status_data is None: + self._series_status_data = series_status.SeriesStatus.default() + return self._series_status_data[self.series] + def __eq__(self, other): return self.name == other.name diff --git a/openstack_releases/series_status.py b/openstack_releases/series_status.py index dad456aa28..60e2aa7da2 100644 --- a/openstack_releases/series_status.py +++ b/openstack_releases/series_status.py @@ -47,6 +47,10 @@ class Series(object): def eol_date(self): return self._data.get('eol-date', None) + @property + def allows_releases(self): + return self.status in ('development', 'maintained') + class SeriesStatus(collections.abc.Mapping): @@ -59,6 +63,12 @@ class SeriesStatus(collections.abc.Mapping): raw_data = cls._load_series_status_data(root_dir) return cls(raw_data) + @classmethod + def default(cls): + module_path = os.path.dirname(__file__) + root_dir = os.path.dirname(module_path) + return cls.from_directory(root_dir) + @staticmethod def _load_series_status_data(root_dir): filename = os.path.join(root_dir, 'deliverables', 'series_status.yaml') diff --git a/openstack_releases/tests/test_validate.py b/openstack_releases/tests/test_validate.py index 6190cdda4a..1765a9017b 100644 --- a/openstack_releases/tests/test_validate.py +++ b/openstack_releases/tests/test_validate.py @@ -28,6 +28,7 @@ from openstack_releases import defaults from openstack_releases import deliverable from openstack_releases import gitutils from openstack_releases import processutils +from openstack_releases import series_status from openstack_releases import yamlutils @@ -1022,6 +1023,141 @@ class TestValidateNewReleasesAtEnd(base.BaseTestCase): self.assertEqual(1, len(self.ctx.errors)) +class TestValidateNewReleasesInOpenSeries(base.BaseTestCase): + + _series_status_data = yamlutils.loads(textwrap.dedent(''' + - name: rocky + status: development + initial-release: 2018-08-30 + - name: queens + status: maintained + initial-release: 2018-02-28 + - name: ocata + status: extended maintenance + initial-release: 2017-02-22 + - name: newton + status: end of life + initial-release: 2016-10-06 + eol-date: 2017-10-25 + ''')) + + def setUp(self): + super().setUp() + self.ctx = validate.ValidationContext() + gitutils.clone_repo(self.ctx.workdir, 'openstack/release-test') + self.series_status = series_status.SeriesStatus( + self._series_status_data) + self.useFixture(fixtures.MockPatch( + 'openstack_releases.deliverable.Deliverable._series_status_data', + self.series_status, + )) + + def test_no_releases(self): + # When we initialize a new series, we won't have any release + # data. That's OK. + deliv = deliverable.Deliverable( + team='team', + series='rocky', + name='name', + data={ + 'artifact-link-mode': 'none', + 'releases': [] + } + ) + validate.validate_new_releases_in_open_series(deliv, self.ctx) + self.ctx.show_summary() + self.assertEqual(0, len(self.ctx.warnings)) + self.assertEqual(0, len(self.ctx.errors)) + + def test_development(self): + deliv = deliverable.Deliverable( + team='team', + series='rocky', + name='name', + data={ + 'artifact-link-mode': 'none', + 'releases': [ + {'version': '10.0.0', + 'projects': [ + {'repo': 'openstack/release-test', + 'hash': 'a26e6a2e8a5e321b2e3517dbb01a7b9a56a8bfd5', + 'tarball-base': 'openstack-release-test'}, + ]}, + ], + } + ) + validate.validate_new_releases_in_open_series(deliv, self.ctx) + self.ctx.show_summary() + self.assertEqual(0, len(self.ctx.warnings)) + self.assertEqual(0, len(self.ctx.errors)) + + def test_maintained(self): + deliv = deliverable.Deliverable( + team='team', + series='queens', + name='name', + data={ + 'artifact-link-mode': 'none', + 'releases': [ + {'version': '10.0.0', + 'projects': [ + {'repo': 'openstack/release-test', + 'hash': 'a26e6a2e8a5e321b2e3517dbb01a7b9a56a8bfd5', + 'tarball-base': 'openstack-release-test'}, + ]}, + ], + } + ) + validate.validate_new_releases_in_open_series(deliv, self.ctx) + self.ctx.show_summary() + self.assertEqual(0, len(self.ctx.warnings)) + self.assertEqual(0, len(self.ctx.errors)) + + def test_extended_maintaintenance(self): + deliv = deliverable.Deliverable( + team='team', + series='ocata', + name='name', + data={ + 'artifact-link-mode': 'none', + 'releases': [ + {'version': '10.0.0', + 'projects': [ + {'repo': 'openstack/release-test', + 'hash': 'a26e6a2e8a5e321b2e3517dbb01a7b9a56a8bfd5', + 'tarball-base': 'openstack-release-test'}, + ]}, + ], + } + ) + validate.validate_new_releases_in_open_series(deliv, self.ctx) + self.ctx.show_summary() + self.assertEqual(0, len(self.ctx.warnings)) + self.assertEqual(1, len(self.ctx.errors)) + + def test_end_of_life(self): + deliv = deliverable.Deliverable( + team='team', + series='newton', + name='name', + data={ + 'artifact-link-mode': 'none', + 'releases': [ + {'version': '10.0.0', + 'projects': [ + {'repo': 'openstack/release-test', + 'hash': 'a26e6a2e8a5e321b2e3517dbb01a7b9a56a8bfd5', + 'tarball-base': 'openstack-release-test'}, + ]}, + ], + } + ) + validate.validate_new_releases_in_open_series(deliv, self.ctx) + self.ctx.show_summary() + self.assertEqual(0, len(self.ctx.warnings)) + self.assertEqual(1, len(self.ctx.errors)) + + class TestValidateVersionNumbers(base.BaseTestCase): def setUp(self):