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 <doug@doughellmann.com>
This commit is contained in:
Doug Hellmann 2018-04-20 11:08:03 -04:00
parent 481185dc20
commit de52a1fefa
4 changed files with 200 additions and 0 deletions

View File

@ -875,6 +875,51 @@ def validate_new_releases_at_end(deliv, context):
print('OK') 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 @applies_to_released
def validate_release_branch_membership(deliv, context): def validate_release_branch_membership(deliv, context):
"Commits being tagged need to be on the right branch." "Commits being tagged need to be on the right branch."
@ -1471,6 +1516,7 @@ def main():
validate_existing_tags, validate_existing_tags,
validate_version_numbers, validate_version_numbers,
validate_new_releases_at_end, validate_new_releases_at_end,
validate_new_releases_in_open_series,
validate_release_branch_membership, validate_release_branch_membership,
validate_tarball_base, validate_tarball_base,
validate_new_releases, validate_new_releases,

View File

@ -25,6 +25,7 @@ import weakref
import pbr.version import pbr.version
from openstack_releases import governance from openstack_releases import governance
from openstack_releases import series_status
from openstack_releases import yamlutils from openstack_releases import yamlutils
@ -330,6 +331,7 @@ class Branch(object):
class Deliverable(object): class Deliverable(object):
_governance_data = None _governance_data = None
_series_status_data = None
def __init__(self, team, series, name, data): def __init__(self, team, series, name, data):
self.team = team self.team = team
@ -522,6 +524,12 @@ class Deliverable(object):
def cycle_highlights(self): def cycle_highlights(self):
return self._data.get('cycle-highlights', []) 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): def __eq__(self, other):
return self.name == other.name return self.name == other.name

View File

@ -47,6 +47,10 @@ class Series(object):
def eol_date(self): def eol_date(self):
return self._data.get('eol-date', None) return self._data.get('eol-date', None)
@property
def allows_releases(self):
return self.status in ('development', 'maintained')
class SeriesStatus(collections.abc.Mapping): class SeriesStatus(collections.abc.Mapping):
@ -59,6 +63,12 @@ class SeriesStatus(collections.abc.Mapping):
raw_data = cls._load_series_status_data(root_dir) raw_data = cls._load_series_status_data(root_dir)
return cls(raw_data) 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 @staticmethod
def _load_series_status_data(root_dir): def _load_series_status_data(root_dir):
filename = os.path.join(root_dir, 'deliverables', 'series_status.yaml') filename = os.path.join(root_dir, 'deliverables', 'series_status.yaml')

View File

@ -28,6 +28,7 @@ from openstack_releases import defaults
from openstack_releases import deliverable from openstack_releases import deliverable
from openstack_releases import gitutils from openstack_releases import gitutils
from openstack_releases import processutils from openstack_releases import processutils
from openstack_releases import series_status
from openstack_releases import yamlutils from openstack_releases import yamlutils
@ -1022,6 +1023,141 @@ class TestValidateNewReleasesAtEnd(base.BaseTestCase):
self.assertEqual(1, len(self.ctx.errors)) 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): class TestValidateVersionNumbers(base.BaseTestCase):
def setUp(self): def setUp(self):