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):