add support for storyboard bug and task trackers

Some projects use storyboard instead of launchpad, so look for one or
the other and try to do some basic validation of the storyboard project
ID.

Change-Id: I94a9507969bcc8bbe2358132761c0935d94cadaa
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
This commit is contained in:
Doug Hellmann 2017-03-16 11:34:24 -04:00
parent 50dd831793
commit d5840915a6
3 changed files with 121 additions and 16 deletions

View File

@ -247,7 +247,8 @@ For deliverable set of projects, we use one YAML file per release
series to hold all of the metadata for all releases and branches of series to hold all of the metadata for all releases and branches of
that deliverable. For each deliverable, we need to track: that deliverable. For each deliverable, we need to track:
* the launchpad project name (such as ``oslo.config``) * the launchpad project name (such as ``oslo.config``) or storyboard
project id (such as ``760``)
* the series (Kilo, Liberty, etc.) * the series (Kilo, Liberty, etc.)
* the release model being used * the release model being used
* for each repository * for each repository
@ -280,6 +281,11 @@ The top level of a deliverable file is a mapping with keys:
``launchpad`` ``launchpad``
The slug name of the launchpad project, suitable for use in URLs. The slug name of the launchpad project, suitable for use in URLs.
(Not needed for projects using storyboard.)
``storyboard``
The ID of the storyboard project, suitable for use in URLs and API
calls. (Not needed for projects using launchpad.)
``release-notes`` ``release-notes``
The URL or URLs to the published release notes for the deliverable The URL or URLs to the published release notes for the deliverable

View File

@ -142,13 +142,10 @@ def validate_series_first(deliverable_info, series_name,
) )
def validate_launchpad(deliverable_info, mk_warning, mk_error): def validate_bugtracker(deliverable_info, mk_warning, mk_error):
"Look for the launchpad project" "Look for the bugtracker info"
try: if 'launchpad' in deliverable_info:
lp_name = deliverable_info['launchpad'] lp_name = deliverable_info['launchpad']
except KeyError:
mk_error('No launchpad project given')
else:
try: try:
lp_resp = requests.get('https://api.launchpad.net/1.0/' + lp_name) lp_resp = requests.get('https://api.launchpad.net/1.0/' + lp_name)
except requests.exceptions.ConnectionError as e: except requests.exceptions.ConnectionError as e:
@ -158,6 +155,35 @@ def validate_launchpad(deliverable_info, mk_warning, mk_error):
else: else:
if (lp_resp.status_code // 100) == 4: if (lp_resp.status_code // 100) == 4:
mk_error('Launchpad project %s does not exist' % lp_name) mk_error('Launchpad project %s does not exist' % lp_name)
elif 'storyboard' in deliverable_info:
try:
sb_id = int(deliverable_info['storyboard'])
except (TypeError, ValueError):
mk_error('Invalid storyboard ID, must be a number: %s' %
deliverable_info['storyboard'])
return
try:
projects_resp = requests.get(
'https://storyboard.openstack.org/api/v1/projects'
)
except requests.exceptions.ConnectionError as e:
# The flakey Launchpad API failed. Don't punish the user for that.
mk_warning('Could not verify storyboard project %s (%s)' %
(sb_id, e))
else:
if (projects_resp.status_code // 100) == 4:
mk_warning(
'Could not verify storyboard project, API call failed.'
)
for project in projects_resp.json():
if sb_id == project.get('id'):
break
else:
mk_error(
'Did not find a storyboard project with ID %s' % sb_id
)
else:
mk_error('No launchpad or storyboard project given')
def validate_team(deliverable_info, team_data, mk_warning, mk_error): def validate_team(deliverable_info, team_data, mk_warning, mk_error):
@ -689,7 +715,7 @@ def main():
print('ERROR: {}'.format(msg)) print('ERROR: {}'.format(msg))
errors.append('{}: {}'.format(filename, msg)) errors.append('{}: {}'.format(filename, msg))
validate_launchpad(deliverable_info, mk_warning, mk_error) validate_bugtracker(deliverable_info, mk_warning, mk_error)
validate_team(deliverable_info, team_data, mk_warning, mk_error) validate_team(deliverable_info, team_data, mk_warning, mk_error)
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)

View File

@ -26,12 +26,12 @@ from openstack_releases import defaults
from openstack_releases.cmds import validate from openstack_releases.cmds import validate
class TestValidateLaunchpad(base.BaseTestCase): class TestValidateBugTracker(base.BaseTestCase):
def test_no_launchpad_name(self): def test_no_tracker(self):
warnings = [] warnings = []
errors = [] errors = []
validate.validate_launchpad( validate.validate_bugtracker(
{}, {},
warnings.append, warnings.append,
errors.append, errors.append,
@ -40,11 +40,11 @@ class TestValidateLaunchpad(base.BaseTestCase):
self.assertEqual(1, len(errors)) self.assertEqual(1, len(errors))
@mock.patch('requests.get') @mock.patch('requests.get')
def test_invalid_launchpad_name(self, get): def test_launchpad_invalid_name(self, get):
get.return_value = mock.Mock(status_code=404) get.return_value = mock.Mock(status_code=404)
warnings = [] warnings = []
errors = [] errors = []
validate.validate_launchpad( validate.validate_bugtracker(
{'launchpad': 'nonsense-name'}, {'launchpad': 'nonsense-name'},
warnings.append, warnings.append,
errors.append, errors.append,
@ -53,11 +53,11 @@ class TestValidateLaunchpad(base.BaseTestCase):
self.assertEqual(1, len(errors)) self.assertEqual(1, len(errors))
@mock.patch('requests.get') @mock.patch('requests.get')
def test_valid_launchpad_name(self, get): def test_launchpad_valid_name(self, get):
get.return_value = mock.Mock(status_code=200) get.return_value = mock.Mock(status_code=200)
warnings = [] warnings = []
errors = [] errors = []
validate.validate_launchpad( validate.validate_bugtracker(
{'launchpad': 'oslo.config'}, {'launchpad': 'oslo.config'},
warnings.append, warnings.append,
errors.append, errors.append,
@ -71,7 +71,7 @@ class TestValidateLaunchpad(base.BaseTestCase):
get.side_effect = requests.exceptions.ConnectionError('testing') get.side_effect = requests.exceptions.ConnectionError('testing')
warnings = [] warnings = []
errors = [] errors = []
validate.validate_launchpad( validate.validate_bugtracker(
{'launchpad': 'oslo.config'}, {'launchpad': 'oslo.config'},
warnings.append, warnings.append,
errors.append, errors.append,
@ -79,6 +79,79 @@ class TestValidateLaunchpad(base.BaseTestCase):
self.assertEqual(1, len(warnings)) self.assertEqual(1, len(warnings))
self.assertEqual(0, len(errors)) self.assertEqual(0, len(errors))
@mock.patch('requests.get')
def test_storyboard_valid_id(self, get):
get.return_value = mock.Mock(status_code=200)
get.return_value.json.return_value = [
{
"name": "openstack-infra/storyboard",
"created_at": "2014-03-12T17:52:19+00:00",
"is_active": True,
"updated_at": None,
"autocreate_branches": False,
"repo_url": None,
"id": 456,
"description": "OpenStack Task Tracking API",
},
{
"name": "openstack-infra/shade",
"created_at": "2015-01-07T20:56:27+00:00",
"is_active": True,
"updated_at": None,
"autocreate_branches": False,
"repo_url": None,
"id": 760,
"description": "Client library for OpenStack...",
}
]
warnings = []
errors = []
validate.validate_bugtracker(
{'storyboard': '760'},
warnings.append,
errors.append,
)
self.assertEqual(0, len(warnings))
self.assertEqual(0, len(errors))
@mock.patch('requests.get')
def test_storyboard_invalid_id(self, get):
get.return_value = mock.Mock(status_code=200)
warnings = []
errors = []
validate.validate_bugtracker(
{'storyboard': 'name-not-id'},
warnings.append,
errors.append,
)
self.assertEqual(0, len(warnings))
self.assertEqual(1, len(errors))
@mock.patch('requests.get')
def test_storyboard_no_such_project(self, get):
get.return_value = mock.Mock(status_code=200)
get.return_value.json.return_value = [
{
"name": "openstack-infra/storyboard",
"created_at": "2014-03-12T17:52:19+00:00",
"is_active": True,
"updated_at": None,
"autocreate_branches": False,
"repo_url": None,
"id": 456,
"description": "OpenStack Task Tracking API",
},
]
warnings = []
errors = []
validate.validate_bugtracker(
{'storyboard': '-760'},
warnings.append,
errors.append,
)
self.assertEqual(0, len(warnings))
self.assertEqual(1, len(errors))
class TestValidateTeam(base.BaseTestCase): class TestValidateTeam(base.BaseTestCase):