From be69bc3581c0add26355170d8dcf610083877877 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Thu, 14 Jun 2018 10:23:12 -0400 Subject: [PATCH] update validation to support eol tags Update the version string validation to allow a series-eol tag. Require that the EOL tag for a deliverable match the series and be applied to all repositories that are part of the deliverable. Extend the Release data object to understand whether its tag is an EOL tag and if so to return the series component from it as eol_series. Story: #2001879 Task: #14344 Change-Id: I40b6822c942b559dc6f18a4b6be6abb5407c4d7c Signed-off-by: Doug Hellmann --- openstack_releases/cmds/validate.py | 55 ++++- openstack_releases/deliverable.py | 10 + openstack_releases/tests/test_deliverable.py | 92 ++++++++ openstack_releases/tests/test_validate.py | 215 +++++++++++++++++++ 4 files changed, 368 insertions(+), 4 deletions(-) diff --git a/openstack_releases/cmds/validate.py b/openstack_releases/cmds/validate.py index 0d9948087b..1b7747582e 100644 --- a/openstack_releases/cmds/validate.py +++ b/openstack_releases/cmds/validate.py @@ -251,6 +251,39 @@ def validate_series_final(deliv, context): print('OK') +@applies_to_released +def validate_series_eol(deliv, context): + "The EOL tag should be applied to the previous release." + + current_release = deliv.releases[-1] + + if not current_release.is_eol: + print('this rule only applies when tagging a series as end-of-life') + return + + # The tag should be applied to all of the repositories for the + # deliverable. + actual_repos = set(p.repo.name for p in current_release.projects) + expected_repos = set(r.name for r in deliv.repos) + error = False + for extra in actual_repos.difference(expected_repos): + error = True + context.error( + 'EOL release %s includes repository %s ' + 'that is not in deliverable' % + (current_release.version, extra) + ) + for missing in expected_repos.difference(actual_repos): + error = True + context.error( + 'release %s is missing %s, ' + 'which appears in the deliverable' % + (current_release.version, missing) + ) + if not error: + print('OK') + + @applies_to_current @applies_to_released @applies_to_cycle @@ -786,6 +819,15 @@ def validate_version_numbers(deliv, context): LOG.debug('checking {}'.format(release.version)) + if release.is_eol: + LOG.debug('Found new EOL tag {} for {}'.format( + release.version, deliv.name)) + if release.eol_series != deliv.series: + context.error( + 'EOL tag {} does not refer to the {} series.'.format( + release.version, deliv.series)) + continue + for project in release.projects: if not gitutils.safe_clone_repo(context.workdir, project.repo.name, @@ -958,13 +1000,17 @@ def validate_new_releases_in_open_series(deliv, context): print('tag exists, skipping further validation') continue - LOG.debug('Found new version {} for {}'.format( - release.version, project.repo)) - new_releases[release.version] = release + if release.is_eol: + LOG.debug('Found new EOL tag {} for {}'.format( + release.version, project.repo)) + else: + 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 = ('deliverable {} has status {!r} for {}' + msg = ('deliverable {} has status {!r} for {} ' 'and cannot have new releases tagged').format( deliv.name, deliv.stable_status, deliv.series) context.error(msg) @@ -1589,6 +1635,7 @@ def main(): validate_series_first, validate_series_final, validate_series_post_final, + validate_series_eol, validate_branch_prefixes, validate_stable_branches, validate_feature_branches, diff --git a/openstack_releases/deliverable.py b/openstack_releases/deliverable.py index e6c94f2659..4ff2916050 100644 --- a/openstack_releases/deliverable.py +++ b/openstack_releases/deliverable.py @@ -300,6 +300,16 @@ class Release(object): or 'b' in self.version ) + @property + def is_eol(self): + return self.version.endswith('-eol') + + @property + def eol_series(self): + if self.is_eol: + return self.version.rpartition('-')[0] + return '' + def __eq__(self, other): return self.version == other.version diff --git a/openstack_releases/tests/test_deliverable.py b/openstack_releases/tests/test_deliverable.py index 97bdc2c7ab..6315bbbb58 100644 --- a/openstack_releases/tests/test_deliverable.py +++ b/openstack_releases/tests/test_deliverable.py @@ -79,3 +79,95 @@ class TestReleaseWasForced(base.BaseTestCase): def test_true(self): r = deliverable.Release('version', [], {'flags': ['forced']}, None) self.assertTrue(r.was_forced) + + +class TestEOLTags(base.BaseTestCase): + + def setUp(self): + super().setUp() + + def test_is_eol_tag_true(self): + deliverable_data = textwrap.dedent(''' + releases: + - version: newton-eol + projects: + - repo: openstack/release-test + hash: a26e6a2e8a5e321b2e3517dbb01a7b9a56a8bfd5 + ''') + deliv = deliverable.Deliverable( + team='team', + series='newton', + name='name', + data=yamlutils.loads(deliverable_data), + ) + self.assertTrue(deliv.releases[-1].is_eol) + + def test_is_eol_tag_false(self): + deliverable_data = textwrap.dedent(''' + releases: + - version: 0.3.0 + projects: + - repo: openstack/release-test + hash: a26e6a2e8a5e321b2e3517dbb01a7b9a56a8bfd5 + ''') + deliv = deliverable.Deliverable( + team='team', + series='newton', + name='name', + data=yamlutils.loads(deliverable_data), + ) + self.assertFalse(deliv.releases[-1].is_eol) + + def test_is_eol_tag_false_typo(self): + deliverable_data = textwrap.dedent(''' + releases: + - version: newton-dol + projects: + - repo: openstack/release-test + hash: a26e6a2e8a5e321b2e3517dbb01a7b9a56a8bfd5 + ''') + deliv = deliverable.Deliverable( + team='team', + series='newton', + name='name', + data=yamlutils.loads(deliverable_data), + ) + self.assertFalse(deliv.releases[-1].is_eol) + + def test_eol_series_for_eol_tag(self): + deliverable_data = textwrap.dedent(''' + releases: + - version: newton-eol + projects: + - repo: openstack/release-test + hash: a26e6a2e8a5e321b2e3517dbb01a7b9a56a8bfd5 + ''') + deliv = deliverable.Deliverable( + team='team', + series='newton', + name='name', + data=yamlutils.loads(deliverable_data), + ) + self.assertEqual( + 'newton', + deliv.releases[-1].eol_series, + ) + + def test_eol_series_for_version_tag(self): + deliverable_data = textwrap.dedent(''' + releases: + - version: 0.3.0 + projects: + - repo: openstack/release-test + hash: a26e6a2e8a5e321b2e3517dbb01a7b9a56a8bfd5 + ''') + deliv = deliverable.Deliverable( + team='team', + series='newton', + name='name', + data=yamlutils.loads(deliverable_data), + ) + self.assertEqual( + '', + deliv.releases[-1].eol_series, + ) diff --git a/openstack_releases/tests/test_validate.py b/openstack_releases/tests/test_validate.py index 1765a9017b..77a721f5c9 100644 --- a/openstack_releases/tests/test_validate.py +++ b/openstack_releases/tests/test_validate.py @@ -1157,6 +1157,28 @@ class TestValidateNewReleasesInOpenSeries(base.BaseTestCase): self.assertEqual(0, len(self.ctx.warnings)) self.assertEqual(1, len(self.ctx.errors)) + def test_eol_in_end_of_life(self): + deliv = deliverable.Deliverable( + team='team', + series='newton', + name='name', + data={ + 'artifact-link-mode': 'none', + 'releases': [ + {'version': 'newton-eol', + '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)) + class TestValidateVersionNumbers(base.BaseTestCase): @@ -1193,6 +1215,69 @@ class TestValidateVersionNumbers(base.BaseTestCase): self.assertEqual(0, len(self.ctx.warnings)) self.assertEqual(1, len(self.ctx.errors)) + def test_valid_version(self): + deliv = deliverable.Deliverable( + team='team', + series='ocata', + name='name', + data={ + 'artifact-link-mode': 'none', + 'releases': [ + {'version': '99.5.0', + 'projects': [ + {'repo': 'openstack/release-test', + 'hash': 'a26e6a2e8a5e321b2e3517dbb01a7b9a56a8bfd5'}, + ]} + ], + } + ) + validate.validate_version_numbers(deliv, self.ctx) + self.ctx.show_summary() + self.assertEqual(0, len(self.ctx.warnings)) + self.assertEqual(0, len(self.ctx.errors)) + + def test_eol_valid_version(self): + deliv = deliverable.Deliverable( + team='team', + series='ocata', + name='name', + data={ + 'artifact-link-mode': 'none', + 'releases': [ + {'version': 'ocata-eol', + 'projects': [ + {'repo': 'openstack/release-test', + 'hash': 'a26e6a2e8a5e321b2e3517dbb01a7b9a56a8bfd5'}, + ]} + ], + } + ) + validate.validate_version_numbers(deliv, self.ctx) + self.ctx.show_summary() + self.assertEqual(0, len(self.ctx.warnings)) + self.assertEqual(0, len(self.ctx.errors)) + + def test_eol_wrong_branch(self): + deliv = deliverable.Deliverable( + team='team', + series='ocata', + name='name', + data={ + 'artifact-link-mode': 'none', + 'releases': [ + {'version': 'newton-eol', + 'projects': [ + {'repo': 'openstack/release-test', + 'hash': 'a26e6a2e8a5e321b2e3517dbb01a7b9a56a8bfd5'}, + ]} + ], + } + ) + validate.validate_version_numbers(deliv, self.ctx) + self.ctx.show_summary() + self.assertEqual(0, len(self.ctx.warnings)) + self.assertEqual(1, len(self.ctx.errors)) + def test_no_releases(self): # When we initialize a new series, we won't have any release # data. That's OK. @@ -2926,6 +3011,136 @@ class TestValidateSeriesFinal(base.BaseTestCase): self.assertEqual(1, len(self.ctx.errors)) +class TestValidateSeriesEOL(base.BaseTestCase): + + def setUp(self): + super().setUp() + self.tmpdir = self.useFixture(fixtures.TempDir()).path + self.ctx = validate.ValidationContext() + + def test_no_releases(self): + deliverable_data = yamlutils.loads(textwrap.dedent(''' + --- + team: Release Management + ''')) + deliv = deliverable.Deliverable( + None, + defaults.RELEASE, + 'test', + deliverable_data, + ) + validate.validate_series_eol( + deliv, + self.ctx, + ) + self.ctx.show_summary() + self.assertEqual(0, len(self.ctx.warnings)) + self.assertEqual(0, len(self.ctx.errors)) + + def test_only_normal(self): + deliverable_data = yamlutils.loads(textwrap.dedent(''' + --- + team: Release Management + releases: + - version: 1.5.1 + projects: + - repo: openstack/automaton + hash: be2885f544637e6ee6139df7dc7bf937925804dd + ''')) + deliv = deliverable.Deliverable( + None, + defaults.RELEASE, + 'test', + deliverable_data, + ) + validate.validate_series_eol( + deliv, + self.ctx, + ) + self.ctx.show_summary() + self.assertEqual(0, len(self.ctx.warnings)) + self.assertEqual(0, len(self.ctx.errors)) + + def test_no_eol(self): + deliverable_data = yamlutils.loads(textwrap.dedent(''' + --- + team: Release Management + releases: + - version: 1.5.1 + projects: + - repo: openstack/automaton + hash: be2885f544637e6ee6139df7dc7bf937925804dd + - version: 1.5.2 + projects: + - repo: openstack/automaton + hash: ce2885f544637e6ee6139df7dc7bf937925804dd + ''')) + deliv = deliverable.Deliverable( + None, + defaults.RELEASE, + 'test', + deliverable_data, + ) + validate.validate_series_eol( + deliv, + self.ctx, + ) + self.ctx.show_summary() + self.assertEqual(0, len(self.ctx.warnings)) + self.assertEqual(0, len(self.ctx.errors)) + + def test_eol_ok(self): + deliverable_data = yamlutils.loads(textwrap.dedent(''' + --- + team: Release Management + releases: + - version: newton-eol + projects: + - repo: openstack/automaton + hash: be2885f544637e6ee6139df7dc7bf937925804dd + ''')) + deliv = deliverable.Deliverable( + None, + 'newton', + 'test', + deliverable_data, + ) + validate.validate_series_eol( + deliv, + self.ctx, + ) + self.ctx.show_summary() + self.assertEqual(0, len(self.ctx.warnings)) + self.assertEqual(0, len(self.ctx.errors)) + + def test_eol_missing_repo(self): + deliverable_data = yamlutils.loads(textwrap.dedent(''' + --- + team: Release Management + releases: + - version: newton-eol + projects: + - repo: openstack/automaton + hash: ce2885f544637e6ee6139df7dc7bf937925804dd + repository-settings: + openstack/automaton: {} + openstack/release-test: {} + ''')) + deliv = deliverable.Deliverable( + None, + 'newton', + 'test', + deliverable_data, + ) + validate.validate_series_eol( + deliv, + self.ctx, + ) + self.ctx.show_summary() + self.assertEqual(0, len(self.ctx.warnings)) + self.assertEqual(1, len(self.ctx.errors)) + + class TestValidatePostSeriesFinal(base.BaseTestCase): def setUp(self):