Introduce EOM tag validation

With the new TC resolution the community replaces Extended Maintenance
with Unmaintained status [1]. To accomodate this, validator needs to be
extended to accept <series>-eom tags that applies at the tip of the
given stable/<series> branch and the new unmaintained/<series> branch
will be cut from that tag.

[1] https://governance.openstack.org/tc/resolutions/20230724-unmaintained-branches.html

Change-Id: I606ba5e5f8fd783d6c3f83356ded27a46e8bac56
This commit is contained in:
Előd Illés 2024-01-04 13:56:56 +01:00
parent 77e8ba577c
commit ceb33ae249
3 changed files with 312 additions and 20 deletions

View File

@ -84,6 +84,7 @@ _USES_PREVER = set([
_VALID_BRANCH_PREFIXES = set([
'stable',
'unmaintained',
'feature',
'bugfix',
])
@ -188,16 +189,18 @@ def skip_existing_tags(f):
return decorated
def skip_em_eol_tags(f):
def skip_em_eom_eol_tags(f):
@functools.wraps(f)
def decorated(deliv, context):
em_or_eol = False
em_or_eom_or_eol = False
for release in deliv.releases:
if '-em' in release.version or '-eol' in release.version:
print('Skipping rule for EM or EOL tagging.')
em_or_eol = True
if ('-em' in release.version or
'-eom' in release.version or
'-eol' in release.version):
print('Skipping rule for EM, EOM or EOL tagging.')
em_or_eom_or_eol = True
break
if not em_or_eol:
if not em_or_eom_or_eol:
return f(deliv, context)
return decorated
@ -441,6 +444,28 @@ def validate_series_eol(deliv, context):
)
@skip_existing_tags
@applies_to_released
def validate_series_eom(deliv, context):
"""The EOM tag should be applied to all repositories."""
current_release = deliv.releases[-1]
if not current_release.is_eom:
print('this rule only applies when tagging a series as unmaintained')
return
if len(deliv.branches) == 0:
context.error('only branched deliverables can be tagged EOM')
_require_tag_on_all_repos(
deliv,
current_release,
'EOM',
context,
)
@skip_existing_tags
@applies_to_released
def validate_series_em(deliv, context):
@ -488,7 +513,7 @@ def validate_series_em(deliv, context):
(current_hash, previous_hash))
@skip_em_eol_tags
@skip_em_eom_eol_tags
def validate_bugtracker(deliv, context):
"Does the bug tracker info link to something that exists?"
lp_name = deliv.launchpad_id
@ -533,7 +558,7 @@ def validate_bugtracker(deliv, context):
context.error('No launchpad or storyboard project given')
@skip_em_eol_tags
@skip_em_eom_eol_tags
def validate_team(deliv, context):
"Look for the team name in the governance data."
try:
@ -548,7 +573,7 @@ def validate_team(deliv, context):
print('owned by team {}'.format(deliv.team))
@skip_em_eol_tags
@skip_em_eom_eol_tags
def validate_release_notes(deliv, context):
"Make sure the release notes page exists, if it is specified."
notes_link = deliv.release_notes
@ -580,7 +605,7 @@ def validate_release_notes(deliv, context):
print('{} OK'.format(link))
@skip_em_eol_tags
@skip_em_eom_eol_tags
def validate_model(deliv, context):
"Require a valid release model"
@ -702,7 +727,7 @@ def get_release_type(deliv, repo, workdir):
return ('python-service', False)
@skip_em_eol_tags
@skip_em_eom_eol_tags
@skip_existing_tags
@applies_to_released
def validate_release_type(deliv, context):
@ -744,7 +769,7 @@ def validate_release_type(deliv, context):
)
@skip_em_eol_tags
@skip_em_eom_eol_tags
@applies_to_released
def validate_tarball_base(deliv, context):
"Does tarball-base match the expected value?"
@ -798,7 +823,7 @@ def validate_tarball_base(deliv, context):
sdist, expected))
@skip_em_eol_tags
@skip_em_eom_eol_tags
@applies_to_released
def validate_build_sdist(deliv, context):
"Can we build an sdist for a python project?"
@ -854,7 +879,7 @@ def validate_build_sdist(deliv, context):
)
@skip_em_eol_tags
@skip_em_eom_eol_tags
@skip_existing_tags
@applies_to_released
def validate_pypi_readme(deliv, context):
@ -874,6 +899,10 @@ def validate_pypi_readme(deliv, context):
print('skipping README validation for EOL tag {}'.format(
latest_release.version))
return
if latest_release.is_eom:
print('skipping README validation for EOM tag {}'.format(
latest_release.version))
return
if latest_release.is_em:
print('skipping README validation for EM tag {}'.format(
latest_release.version))
@ -908,7 +937,7 @@ def validate_pypi_readme(deliv, context):
print('OK')
@skip_em_eol_tags
@skip_em_eom_eol_tags
@skip_existing_tags
@applies_to_released
def validate_pypi_permissions(deliv, context):
@ -1120,6 +1149,20 @@ def validate_version_numbers(deliv, context):
release.version, deliv.series))
continue
if release.is_eom:
LOG.debug('Found new EOM tag {} for {}'.format(
release.version, deliv.name))
if deliv.is_independent:
context.warning(
'EOM tag {} on independent deliverable, branch not validated'.format(
release.version))
continue
if release.eom_series != deliv.series:
context.error(
'EOM tag {} does not refer to the {} series.'.format(
release.version, deliv.series))
continue
if release.is_em:
LOG.debug('Found new EM tag {} for {}'.format(
release.version, deliv.name))
@ -1314,7 +1357,7 @@ def validate_new_releases_at_end(deliv, context):
print('OK')
@skip_em_eol_tags
@skip_em_eom_eol_tags
@skip_existing_tags
@applies_to_released
def validate_new_releases_in_open_series(deliv, context):
@ -1467,7 +1510,7 @@ def validate_release_branch_membership(deliv, context):
prev_version[project.repo.name] = release.version
@skip_em_eol_tags
@skip_em_eom_eol_tags
@applies_to_current
@applies_to_released
def validate_new_releases(deliv, context):
@ -2004,6 +2047,7 @@ def main():
validate_series_final,
validate_pre_release_progression,
validate_series_eol,
validate_series_eom,
validate_series_em,
validate_branch_prefixes,
validate_stable_branches,

View File

@ -59,11 +59,12 @@ def _safe_semver(v):
def _version_sort_key(release):
"""Return a value we can compare for sorting."""
# NOTE(dhellmann): We want EOL and EM tags to sort last. This assumes we
# won't have more than 1000 major releases of anything, and I
# surely hope that is a safe assumption.
# NOTE(dhellmann): We want EOL, EOM and EM tags to sort last. This
# assumes we won't have more than 1000 major releases of anything,
# and I surely hope that is a safe assumption.
version_string = release['version']
if version_string.endswith('-eol') or \
version_string.endswith('-eom') or \
version_string.endswith('-em') or \
version_string.endswith('-last'):
return _safe_semver('1000.0.0')
@ -335,6 +336,16 @@ class Release(object):
return self.version.rpartition('-')[0]
return ''
@property
def is_eom(self):
return self.version.endswith('-eom')
@property
def eom_series(self):
if self.is_eom:
return self.version.rpartition('-')[0]
return ''
@property
def is_em(self):
return self.version.endswith('-em')

View File

@ -989,6 +989,9 @@ class TestValidateNewReleasesInOpenSeries(base.BaseTestCase):
- name: queens
status: maintained
initial-release: 2018-02-28
- name: pike
status: unmaintained
initial-release: 2017-08-30
- name: ocata
status: extended maintenance
initial-release: 2017-02-22
@ -1070,6 +1073,28 @@ class TestValidateNewReleasesInOpenSeries(base.BaseTestCase):
self.assertEqual(0, len(self.ctx.warnings))
self.assertEqual(0, len(self.ctx.errors))
def test_unmaintained(self):
deliv = deliverable.Deliverable(
team='team',
series='pike',
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_extended_maintaintenance(self):
deliv = deliverable.Deliverable(
team='team',
@ -1264,6 +1289,27 @@ class TestValidateVersionNumbers(base.BaseTestCase):
self.assertEqual(0, len(self.ctx.warnings))
self.assertEqual(0, len(self.ctx.errors))
def test_eom_valid_version(self):
deliv = deliverable.Deliverable(
team='team',
series='ocata',
name='name',
data={
'artifact-link-mode': 'none',
'releases': [
{'version': 'ocata-eom',
'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_em_valid_version(self):
deliv = deliverable.Deliverable(
team='team',
@ -1306,6 +1352,27 @@ class TestValidateVersionNumbers(base.BaseTestCase):
self.assertEqual(0, len(self.ctx.warnings))
self.assertEqual(1, len(self.ctx.errors))
def test_eom_wrong_branch(self):
deliv = deliverable.Deliverable(
team='team',
series='ocata',
name='name',
data={
'artifact-link-mode': 'none',
'releases': [
{'version': 'newton-eom',
'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_em_wrong_branch(self):
deliv = deliverable.Deliverable(
team='team',
@ -3492,6 +3559,176 @@ class TestValidateSeriesEOL(base.BaseTestCase):
self.assertEqual(1, len(self.ctx.errors))
class TestValidateSeriesEOM(base.BaseTestCase):
def setUp(self):
super().setUp()
self.tmpdir = self.useFixture(fixtures.TempDir()).path
self.ctx = validate.ValidationContext()
self.useFixture(fixtures.MonkeyPatch(
'openstack_releases.cmds.validate.includes_new_tag',
mock.Mock(return_value=True),
))
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_eom(
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_eom(
deliv,
self.ctx,
)
self.ctx.show_summary()
self.assertEqual(0, len(self.ctx.warnings))
self.assertEqual(0, len(self.ctx.errors))
def test_no_eom(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_eom(
deliv,
self.ctx,
)
self.ctx.show_summary()
self.assertEqual(0, len(self.ctx.warnings))
self.assertEqual(0, len(self.ctx.errors))
def test_eom_ok(self):
deliverable_data = yamlutils.loads(textwrap.dedent('''
---
team: Release Management
releases:
- version: 1.5.2
projects:
- repo: openstack/automaton
hash: ce2885f544637e6ee6139df7dc7bf937925804dd
- version: newton-eom
projects:
- repo: openstack/automaton
hash: be2885f544637e6ee6139df7dc7bf937925804dd
branches:
- name: stable/newton
location: 1.5.2
'''))
deliv = deliverable.Deliverable(
None,
'newton',
'test',
deliverable_data,
)
validate.validate_series_eom(
deliv,
self.ctx,
)
self.ctx.show_summary()
self.assertEqual(0, len(self.ctx.warnings))
self.assertEqual(0, len(self.ctx.errors))
def test_eom_missing_repo(self):
deliverable_data = yamlutils.loads(textwrap.dedent('''
---
team: Release Management
releases:
- version: newton-eom
projects:
- repo: openstack/automaton
hash: ce2885f544637e6ee6139df7dc7bf937925804dd
branches:
- name: stable/newton
location: 1.2.3
repository-settings:
openstack/automaton: {}
openstack/release-test: {}
'''))
deliv = deliverable.Deliverable(
None,
'newton',
'test',
deliverable_data,
)
validate.validate_series_eom(
deliv,
self.ctx,
)
self.ctx.show_summary()
self.assertEqual(0, len(self.ctx.warnings))
self.assertEqual(1, len(self.ctx.errors))
def test_eom_branchless(self):
deliverable_data = yamlutils.loads(textwrap.dedent('''
---
team: Release Management
releases:
- version: newton-eom
projects:
- repo: openstack/automaton
hash: ce2885f544637e6ee6139df7dc7bf937925804dd
repository-settings:
openstack/automaton: {}
'''))
deliv = deliverable.Deliverable(
None,
'newton',
'test',
deliverable_data,
)
validate.validate_series_eom(
deliv,
self.ctx,
)
self.ctx.show_summary()
self.assertEqual(0, len(self.ctx.warnings))
self.assertEqual(1, len(self.ctx.errors))
class TestValidateSeriesEM(base.BaseTestCase):
def setUp(self):