provide separate release types for server and non-server deliverables

Change the 'std' release type to 'python-server' and add a
'python-pypi' release type for deliverables that are published to
PyPI.

Separate the release job validation from the validation of release
version numbers and other settings to make the logic clearer.

Add a new function to determine the release type for a project, either
by checking the explicit value or guessing.

Update the unit tests that relied on 'std'.

Remove a unit test that tested a code path that has been removed.

Change-Id: I704ec75fec61ecb6ee379239a5fa8612cb01b426
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
This commit is contained in:
Doug Hellmann 2017-10-26 09:42:41 -04:00
parent eed9cb73fc
commit 9dbb30dce1
16 changed files with 217 additions and 100 deletions

View File

@ -397,10 +397,14 @@ The top level of a deliverable file is a mapping with keys:
``release-type`` ``release-type``
This (optional) key sets the level of validation for the versions numbers. This (optional) key sets the level of validation for the versions numbers.
``std`` ``python-server``
Default: Enforces 3 digit semver version numbers in releases and allows Default: Enforces 3 digit semver version numbers in releases and allows
for common alpha, beta and dev releases. This should be appropriate for for common alpha, beta and dev releases. This should be appropriate for
most OpenStack release requirements. most OpenStack component release requirements.
``python-pypi``
Like ``python-server`` but requires the jobs to publish the component
to the Python Package Index (PyPI).
``xstatic`` ``xstatic``
Allows a more flexible versioning in line with xstatic package guidelines Allows a more flexible versioning in line with xstatic package guidelines

View File

@ -1,7 +1,6 @@
--- ---
launchpad: ldappool launchpad: ldappool
team: keystone team: keystone
release-type: std
type: library type: library
releases: releases:
- version: 2.0.0 - version: 2.0.0

View File

@ -2,7 +2,6 @@
launchpad: openstack-doc-tools launchpad: openstack-doc-tools
team: Documentation team: Documentation
type: library type: library
release-type: std
releases: releases:
- version: 0.1.0 - version: 0.1.0
projects: projects:

View File

@ -1,7 +1,6 @@
--- ---
launchpad: os-testr launchpad: os-testr
team: Quality Assurance team: Quality Assurance
release-type: std
type: library type: library
include-pypi-link: yes include-pypi-link: yes
releases: releases:

View File

@ -1,7 +1,6 @@
--- ---
launchpad: nova launchpad: nova
team: nova team: nova
release-type: std
type: library type: library
releases: releases:
- version: 0.1.0 - version: 0.1.0

View File

@ -1,7 +1,6 @@
--- ---
launchpad: sqlalchemy-migrate launchpad: sqlalchemy-migrate
team: sqlalchemy-migrate team: sqlalchemy-migrate
release-type: std
type: library type: library
releases: releases:
- version: 0.6.1 - version: 0.6.1

View File

@ -1,7 +1,7 @@
--- ---
launchpad: whereto launchpad: whereto
team: whereto team: whereto
release-type: std release-type: python-pypi
type: other type: other
releases: releases:
- version: 0.1.0 - version: 0.1.0

View File

@ -2,6 +2,7 @@
launchpad: nova launchpad: nova
team: 'Release Management' team: 'Release Management'
type: other type: other
release-type: python-pypi
branches: branches:
- name: stable/queens - name: stable/queens
location: 0.10.2 location: 0.10.2

View File

@ -1,6 +1,7 @@
--- ---
launchpad: tripleo-common launchpad: tripleo-common
release-model: cycle-trailing release-model: cycle-trailing
release-type: python-pypi
team: tripleo team: tripleo
type: other type: other
releases: releases:

View File

@ -348,6 +348,66 @@ def validate_gitreview(deliverable_info, workdir, mk_warning, mk_error):
mk_error('%s has no .gitreview file' % (project['repo'],)) mk_error('%s has no .gitreview file' % (project['repo'],))
def get_release_type(deliverable_info, project, workdir):
"""Return tuple with release type and boolean indicating whether it
was explicitly set.
"""
if 'release-type' in deliverable_info:
return (deliverable_info['release-type'], True)
if deliverable_info.get('type') == 'library':
return ('python-pypi', False)
if puppetutils.looks_like_a_module(workdir, project['repo']):
return ('puppet', False)
if npmutils.looks_like_a_module(workdir, project['repo']):
return ('nodejs', False)
return ('python-server', False)
def validate_release_type(deliverable_info,
zuul_projects,
series_name,
workdir,
mk_warning,
mk_error):
"""Apply validation rules for the deliverable based on 'release-type'.
"""
link_mode = deliverable_info.get('artifact-link-mode', 'tarball')
if link_mode == 'none':
print('link-mode is "none", skipping release-type checks')
return
if not deliverable_info.get('releases'):
print('no releases listed, skipping release-type checks')
return
for release in deliverable_info.get('releases', []):
for project in release['projects']:
print('checking release-type for {}'.format(project['repo']))
release_type, was_explicit = get_release_type(
deliverable_info, project, workdir,
)
if was_explicit:
print('found explicit release-type {!r}'.format(
release_type))
else:
print('release-type not given, '
'guessing {!r}'.format(release_type))
project_config.require_release_jobs_for_repo(
deliverable_info, zuul_projects,
project['repo'],
release_type, mk_warning, mk_error,
)
def validate_releases(deliverable_info, zuul_projects, def validate_releases(deliverable_info, zuul_projects,
series_name, series_name,
workdir, workdir,
@ -481,44 +541,21 @@ def validate_releases(deliverable_info, zuul_projects,
(prev_version, ', '.join(sorted(prev_projects)))) (prev_version, ', '.join(sorted(prev_projects))))
else: else:
# We change this default to be more release_type, was_explicit = get_release_type(
# language-specific before testing the release deliverable_info, project, workdir,
# jobs.
default_release_type = 'std'
# If we have an explicit release type, we can
# bypass some of the checks for languages
# other than python.
explicit_release_type = deliverable_info.get(
'release-type',
) )
if explicit_release_type: if was_explicit:
print('found explicit release-type {!r}'.format( print('found explicit release-type {!r}'.format(
explicit_release_type)) release_type))
else: else:
print('release-type not given, ' print('release-type not given, '
'determining automatically') 'guessing {!r}'.format(release_type))
is_puppet = (
explicit_release_type == 'puppet' or
((not explicit_release_type) and
puppetutils.looks_like_a_module(workdir,
project['repo']))
)
is_nodejs = (
explicit_release_type == 'nodejs' or
((not explicit_release_type) and
npmutils.looks_like_a_module(workdir,
project['repo']))
)
# If this is a puppet module, ensure # If this is a puppet module, ensure
# that the tag and metadata file # that the tag and metadata file
# match. # match.
if is_puppet: if release_type == 'puppet':
print('applying puppet version rules') print('applying puppet version rules')
default_release_type = 'puppet'
puppet_ver = puppetutils.get_version( puppet_ver = puppetutils.get_version(
workdir, project['repo']) workdir, project['repo'])
if puppet_ver != release['version']: if puppet_ver != release['version']:
@ -534,9 +571,8 @@ def validate_releases(deliverable_info, zuul_projects,
# If this is a npm module, ensure # If this is a npm module, ensure
# that the tag and metadata file # that the tag and metadata file
# match. # match.
if is_nodejs: if release_type == 'nodejs':
print('applying nodejs version rules') print('applying nodejs version rules')
default_release_type = 'nodejs'
npm_ver = npmutils.get_version( npm_ver = npmutils.get_version(
workdir, project['repo']) workdir, project['repo'])
if npm_ver != release['version']: if npm_ver != release['version']:
@ -549,18 +585,6 @@ def validate_releases(deliverable_info, zuul_projects,
) )
) )
# Check for release jobs (if we ship a tarball)
release_type = deliverable_info.get(
'release-type',
default_release_type,
)
if link_mode != 'none':
project_config.require_release_jobs_for_repo(
deliverable_info, zuul_projects,
project['repo'],
release_type, mk_warning, mk_error,
)
for e in versionutils.validate_version( for e in versionutils.validate_version(
release['version'], release['version'],
release_type=release_type, release_type=release_type,
@ -971,6 +995,14 @@ def main():
validate_release_notes(deliverable_info, mk_warning, mk_error) validate_release_notes(deliverable_info, mk_warning, mk_error)
validate_type(deliverable_info, mk_warning, mk_error) validate_type(deliverable_info, mk_warning, mk_error)
validate_model(deliverable_info, series_name, mk_warning, mk_error) validate_model(deliverable_info, series_name, mk_warning, mk_error)
validate_release_type(
deliverable_info,
zuul_projects,
series_name,
workdir,
mk_warning,
mk_error,
)
validate_gitreview(deliverable_info, workdir, mk_warning, mk_error) validate_gitreview(deliverable_info, workdir, mk_warning, mk_error)
validate_releases( validate_releases(
deliverable_info, deliverable_info,

View File

@ -81,10 +81,12 @@ def get_zuul_project_data(url=ZUUL_PROJECTS_URL):
# Which jobs are needed for which release types. # Which jobs are needed for which release types.
_RELEASE_JOBS_FOR_TYPE = { _RELEASE_JOBS_FOR_TYPE = {
'std': [ 'python-server': [
'publish-to-pypi',
'release-openstack-server', 'release-openstack-server',
], ],
'python-pypi': [
'publish-to-pypi',
],
'neutron': [ 'neutron': [
'publish-to-pypi-neutron', 'publish-to-pypi-neutron',
], ],
@ -140,7 +142,7 @@ def require_release_jobs_for_repo(deliverable_info, zuul_projects, repo,
# jobs, because we want projects to use the templates. # jobs, because we want projects to use the templates.
expected_jobs = _RELEASE_JOBS_FOR_TYPE.get( expected_jobs = _RELEASE_JOBS_FOR_TYPE.get(
release_type, release_type,
_RELEASE_JOBS_FOR_TYPE['std'], _RELEASE_JOBS_FOR_TYPE['python-server'],
) )
if expected_jobs: if expected_jobs:
found_jobs = [ found_jobs = [

View File

@ -35,7 +35,8 @@ properties:
type: "object" type: "object"
release-type: release-type:
type: "string" type: "string"
enum: [ "std", "xstatic", "fuel", "nodejs", "puppet", "neutron", "horizon" ] enum: [ "python-server", "python-pypi", "xstatic", "fuel",
"nodejs", "puppet", "neutron", "horizon" ]
stable-branch-type: stable-branch-type:
type: "string" type: "string"
enum: [ "std", "tagless", "upstream" ] enum: [ "std", "tagless", "upstream" ]

View File

@ -103,7 +103,7 @@ class TestReleaseJobsStandard(base.BaseTestCase):
deliverable_info, deliverable_info,
zuul_projects, zuul_projects,
'openstack/releases', 'openstack/releases',
'std', 'python-pypi',
warnings.append, warnings.append,
errors.append, errors.append,
) )
@ -128,7 +128,7 @@ class TestReleaseJobsStandard(base.BaseTestCase):
deliverable_info, deliverable_info,
zuul_projects, zuul_projects,
'openstack/releases', 'openstack/releases',
'std', 'python-pypi',
warnings.append, warnings.append,
errors.append, errors.append,
) )

View File

@ -417,33 +417,6 @@ class TestValidateReleases(base.BaseTestCase):
self.tmpdir = self.useFixture(fixtures.TempDir()).path self.tmpdir = self.useFixture(fixtures.TempDir()).path
gitutils.clone_repo(self.tmpdir, 'openstack/release-test') gitutils.clone_repo(self.tmpdir, 'openstack/release-test')
@mock.patch('openstack_releases.project_config.require_release_jobs_for_repo')
def test_check_release_jobs(self, check_jobs):
deliverable_info = {
'releases': [
{'version': '99.5.0',
'projects': [
{'repo': 'openstack/release-test',
'hash': '218c9c82f168f1db681b27842b5a829428c6b5e1',
'tarball-base': 'openstack-release-test'},
]}
],
}
warnings = []
errors = []
validate.validate_releases(
deliverable_info,
{'validate-projects-by-name': {}},
'queens',
self.tmpdir,
warnings.append,
errors.append,
)
print(warnings, errors)
self.assertEqual(0, len(warnings))
self.assertEqual(0, len(errors))
check_jobs.assert_called_once()
def test_invalid_hash(self): def test_invalid_hash(self):
deliverable_info = { deliverable_info = {
'artifact-link-mode': 'none', 'artifact-link-mode': 'none',
@ -806,6 +779,113 @@ class TestValidateReleases(base.BaseTestCase):
self.assertEqual(1, len(errors)) self.assertEqual(1, len(errors))
class TestGetReleaseType(base.BaseTestCase):
def setUp(self):
super(TestGetReleaseType, self).setUp()
self.tmpdir = self.useFixture(fixtures.TempDir()).path
def test_explicit(self):
deliverable_info = {
'artifact-link-mode': 'none',
'release-type': 'explicitly-set',
'releases': [
{'version': '99.1.0',
'projects': [
{'repo': 'openstack/puppet-watcher',
'hash': '1e7baef27139f69a83e1fe28686bb72ee7e1d6fa'},
]}
],
}
release_type, explicit = validate.get_release_type(
deliverable_info,
deliverable_info['releases'][0]['projects'][0],
self.tmpdir,
)
self.assertEqual(('explicitly-set', True), (release_type, explicit))
def test_library(self):
deliverable_info = {
'artifact-link-mode': 'none',
'type': 'library',
'releases': [
{'version': '99.1.0',
'projects': [
{'repo': 'openstack/puppet-watcher',
'hash': '1e7baef27139f69a83e1fe28686bb72ee7e1d6fa'},
]}
],
}
release_type, explicit = validate.get_release_type(
deliverable_info,
deliverable_info['releases'][0]['projects'][0],
self.tmpdir,
)
self.assertEqual(('python-pypi', False), (release_type, explicit))
@mock.patch('openstack_releases.puppetutils.looks_like_a_module')
def test_puppet(self, llam):
llam.return_value = True
deliverable_info = {
'artifact-link-mode': 'none',
'releases': [
{'version': '99.1.0',
'projects': [
{'repo': 'openstack/puppet-watcher',
'hash': '1e7baef27139f69a83e1fe28686bb72ee7e1d6fa'},
]}
],
}
release_type, explicit = validate.get_release_type(
deliverable_info,
deliverable_info['releases'][0]['projects'][0],
self.tmpdir,
)
self.assertEqual(('puppet', False), (release_type, explicit))
@mock.patch('openstack_releases.npmutils.looks_like_a_module')
def test_nodejs(self, llam):
llam.return_value = True
deliverable_info = {
'artifact-link-mode': 'none',
'releases': [
{'version': '99.1.0',
'projects': [
{'repo': 'openstack/puppet-watcher',
'hash': '1e7baef27139f69a83e1fe28686bb72ee7e1d6fa'},
]}
],
}
release_type, explicit = validate.get_release_type(
deliverable_info,
deliverable_info['releases'][0]['projects'][0],
self.tmpdir,
)
self.assertEqual(('nodejs', False), (release_type, explicit))
@mock.patch('openstack_releases.puppetutils.looks_like_a_module')
@mock.patch('openstack_releases.npmutils.looks_like_a_module')
def test_python_server(self, nllam, pllam):
pllam.return_value = False
nllam.return_value = False
deliverable_info = {
'artifact-link-mode': 'none',
'releases': [
{'version': '99.1.0',
'projects': [
{'repo': 'openstack/puppet-watcher',
'hash': '1e7baef27139f69a83e1fe28686bb72ee7e1d6fa'},
]}
],
}
release_type, explicit = validate.get_release_type(
deliverable_info,
deliverable_info['releases'][0]['projects'][0],
self.tmpdir,
)
self.assertEqual(('python-server', False), (release_type, explicit))
class TestPuppetUtils(base.BaseTestCase): class TestPuppetUtils(base.BaseTestCase):
def setUp(self): def setUp(self):

View File

@ -19,15 +19,15 @@ from openstack_releases import versionutils
class TestValidateVersion(base.BaseTestCase): class TestValidateVersion(base.BaseTestCase):
def test_valid_std(self): def test_valid_python_server(self):
errors = list(versionutils.validate_version('1.2.3')) errors = list(versionutils.validate_version('1.2.3'))
self.assertEqual(0, len(errors)) self.assertEqual(0, len(errors))
def test_invalid_std(self): def test_invalid_python_server(self):
errors = list(versionutils.validate_version('1.2.3.4')) errors = list(versionutils.validate_version('1.2.3.4'))
self.assertEqual(1, len(errors)) self.assertEqual(1, len(errors))
def test_empty_std(self): def test_empty_python_server(self):
errors = list(versionutils.validate_version('')) errors = list(versionutils.validate_version(''))
self.assertEqual(1, len(errors)) self.assertEqual(1, len(errors))

View File

@ -26,22 +26,23 @@ import pbr.version
# 3. canonicalise: The function used to canonicalise the *Version object. # 3. canonicalise: The function used to canonicalise the *Version object.
# Used to verify that the version string is already in the # Used to verify that the version string is already in the
# canonical form # canonical form
_VALIDATORS = {'std': (pbr.version.SemanticVersion.from_pip_string, _VALIDATORS = {'python-server': (pbr.version.SemanticVersion.from_pip_string,
ValueError, ValueError,
lambda x: x.release_string()), lambda x: x.release_string()),
'xstatic': (packaging.version.Version, 'xstatic': (packaging.version.Version,
packaging.version.InvalidVersion, packaging.version.InvalidVersion,
lambda x: str(x)), lambda x: str(x)),
} }
_VALIDATORS['fuel'] = _VALIDATORS['std'] _VALIDATORS['fuel'] = _VALIDATORS['python-server']
_VALIDATORS['openstack-manuals'] = _VALIDATORS['std'] _VALIDATORS['openstack-manuals'] = _VALIDATORS['python-server']
_VALIDATORS['puppet'] = _VALIDATORS['std'] _VALIDATORS['puppet'] = _VALIDATORS['python-server']
_VALIDATORS['nodejs'] = _VALIDATORS['std'] _VALIDATORS['nodejs'] = _VALIDATORS['python-server']
_VALIDATORS['neutron'] = _VALIDATORS['std'] _VALIDATORS['neutron'] = _VALIDATORS['python-server']
_VALIDATORS['horizon'] = _VALIDATORS['std'] _VALIDATORS['horizon'] = _VALIDATORS['python-server']
_VALIDATORS['python-pypi'] = _VALIDATORS['python-server']
def validate_version(versionstr, release_type='std', pre_ok=True): def validate_version(versionstr, release_type='python-server', pre_ok=True):
"""Given a version string, yield error messages if it is "bad" """Given a version string, yield error messages if it is "bad"
Apply our SemVer rules to version strings and report all issues. Apply our SemVer rules to version strings and report all issues.
@ -54,8 +55,8 @@ def validate_version(versionstr, release_type='std', pre_ok=True):
'model does not allow for it' % versionstr) 'model does not allow for it' % versionstr)
if release_type not in _VALIDATORS: if release_type not in _VALIDATORS:
yield 'Release Type %r not valid using \'std\' instead' % release_type yield 'Release Type %r not valid using \'python-server\' instead' % release_type
release_type = 'std' release_type = 'python-server'
constructor, exception, canonicalise = _VALIDATORS[release_type] constructor, exception, canonicalise = _VALIDATORS[release_type]
try: try:
@ -70,7 +71,7 @@ def validate_version(versionstr, release_type='std', pre_ok=True):
(versionstr, canonical) (versionstr, canonical)
def canonical_version(versionstr, release_type='std'): def canonical_version(versionstr, release_type='python-server'):
"""Given a version string verify it is in the canonical form.""" """Given a version string verify it is in the canonical form."""
errors = list(validate_version(versionstr, release_type)) errors = list(validate_version(versionstr, release_type))
if errors: if errors: