From 4c5d4c6d1c13b588498c53b3e6643e76ecdd55ff Mon Sep 17 00:00:00 2001 From: Ondrej Nosek Date: Aug 05 2019 13:41:16 +0000 Subject: Block retiring in released branches It checks Fedora release state and it restricts retiring operation for other states than 'pending'. States are obtained from service here: https://bodhi.fedoraproject.org/releases/ Unknown releases are allowed to be retired. Fixes: #337 JIRA: COMPOSE-3690 Signed-off-by: Ondrej Nosek --- diff --git a/conf/etc/rpkg/fedpkg-stage.conf b/conf/etc/rpkg/fedpkg-stage.conf index c4561f5..a5324db 100644 --- a/conf/etc/rpkg/fedpkg-stage.conf +++ b/conf/etc/rpkg/fedpkg-stage.conf @@ -40,6 +40,7 @@ git_excludes = [fedpkg-stage.bodhi] # Refer to fedpkg.conf staging = True +releases_service = https://bodhi.stg.fedoraproject.org/releases/%(release)s [fedpkg-stage.mbs] auth_method = oidc diff --git a/conf/etc/rpkg/fedpkg.conf b/conf/etc/rpkg/fedpkg.conf index 3024518..d181940 100644 --- a/conf/etc/rpkg/fedpkg.conf +++ b/conf/etc/rpkg/fedpkg.conf @@ -42,6 +42,7 @@ git_excludes = # different instance. Instead, --staging is available to switch to the stage # bodhi, and production is used without providing --staging. staging = False +releases_service = https://bodhi.fedoraproject.org/releases/%(release)s [fedpkg.mbs] auth_method = oidc diff --git a/fedpkg/cli.py b/fedpkg/cli.py index 8e73f0f..e584e51 100644 --- a/fedpkg/cli.py +++ b/fedpkg/cli.py @@ -32,10 +32,9 @@ from six.moves.urllib_parse import urlparse from pyrpkg import rpkgError from fedpkg.bugzilla import BugzillaClient from fedpkg.utils import ( - get_release_branches, sl_list_to_dict, verify_sls, new_pagure_issue, - get_pagure_token, is_epel, assert_valid_epel_package, - assert_new_tests_repo, get_dist_git_url, get_stream_branches, - expand_release) + get_fedora_release_state, get_release_branches, sl_list_to_dict, verify_sls, + new_pagure_issue, get_pagure_token, is_epel, assert_valid_epel_package, + assert_new_tests_repo, get_dist_git_url, get_stream_branches, expand_release) RELEASE_BRANCH_REGEX = r'^(f\d+|el\d+|epel\d+)$' LOCAL_PACKAGE_CONFIG = 'package.cfg' @@ -1148,3 +1147,16 @@ targets to build the package for a particular stream. else: print('Fedora: {0}'.format(_join(releases['fedora']))) print('EPEL: {0}'.format(_join(releases['epel']))) + + def retire(self): + """ + Runs the rpkg retire command after check. Check includes reading the state + of Fedora release. + """ + state = get_fedora_release_state(self.config, self.name, self.cmd.branch_merge) + + if state is None or state == 'pending': + super(fedpkgClient, self).retire() + else: + self.log.error("Fedora release (%s) is in state '%s' - retire operation " + "is not allowed." % (self.cmd.branch_merge, state)) diff --git a/fedpkg/utils.py b/fedpkg/utils.py index fcaa6ec..3f084ee 100644 --- a/fedpkg/utils.py +++ b/fedpkg/utils.py @@ -399,3 +399,40 @@ def expand_release(rel, active_releases): return [rel] else: return None + + +def get_fedora_release_state(config, cli_name, release): + """ + Queries service page for release state. Query result is returned as json dict. + + :param config: ConfigParser object + :param cli_name: string of the CLI's name (e.g. fedpkg) + :param str release: short release name. Example: F29, F30, F29M, F30C, ... + :return: state of the release or None if there is no such release + :rtype: str + """ + try: + # url of the release service. It needs to be expanded by release name + releases_service_url = config.get('{0}.bodhi'.format(cli_name), + 'releases_service', + vars={'release': release}) + except (ValueError, NoOptionError, NoSectionError) as e: + raise rpkgError('Could not get release state for Fedora ' + '({0}): {1}.'.format(release, str(e))) + + try: + rv = requests.get(releases_service_url, timeout=60) + except ConnectionError as error: + error_msg = ('The connection to Bodhi failed while trying to get ' + 'release state. The error was: {0}'.format(str(error))) + raise rpkgError(error_msg) + + if rv.status_code == 404: + # release wasn't found + return None + elif not rv.ok: + base_error_msg = ('The following error occurred while trying to ' + 'get the release state in Bodhi: {0}') + raise rpkgError(base_error_msg.format(rv.text)) + + return rv.json().get('state') diff --git a/test/fedpkg-test.conf b/test/fedpkg-test.conf index 3587bc9..22ad1e2 100644 --- a/test/fedpkg-test.conf +++ b/test/fedpkg-test.conf @@ -12,6 +12,7 @@ kerberos_realms = FEDORAPROJECT.ORG [fedpkg.bodhi] staging = False +releases_service = https://bodhi.fedoraproject.org/releases/%(release)s [fedpkg.bugzilla] url = https://bugzilla.example.com diff --git a/test/test_cli.py b/test/test_cli.py index aa0d618..58dd0d9 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -2311,3 +2311,53 @@ class TestReleasesInfo(CliTestCase): def test_print_releases_in_default(self, mock_grb): self.assert_output_releases('Fedora: f29 f28\nEPEL: el6 epel7') + + +class TestRetire(CliTestCase): + """ + Test retire operation with additional fedpkg Fedora release checking + """ + + @patch('requests.get') + def retire_release(self, release_state, mock_get): + mock_rv = Mock() + if release_state: + mock_rv.ok = True + mock_rv.json.return_value = { + 'state': release_state, + } + else: + mock_rv.status_code = 404 + mock_get.return_value = mock_rv + + with patch('sys.argv', ['fedpkg', '--release', 'f30', 'retire', 'retire_message']): + cli = self.new_cli(cfg='fedpkg-test.conf') + # retire method in rpkg would be called, but there is not environment configured + # therefore just Exception is catched + with self.assertRaises(Exception): + cli.args.path = '/repo_path' + cli.retire() + + @patch('requests.get') + def do_not_retire_release(self, release_state, mock_get): + mock_rv = Mock() + mock_rv.ok = True + mock_rv.json.return_value = { + 'state': release_state, + } + mock_get.return_value = mock_rv + + with patch('sys.argv', ['fedpkg', '--release', 'f30', 'retire', 'retire_message']): + cli = self.new_cli(cfg='fedpkg-test.conf') + # retire is terminated after check + cli.args.path = '/repo_path' + cli.retire() + + def test_retire_fedora_release(self): + self.do_not_retire_release("disabled") + self.retire_release("pending") + self.do_not_retire_release("frozen") + self.do_not_retire_release("current") + self.do_not_retire_release("archived") + # unknown fedora (or other) release + self.retire_release(None) diff --git a/test/test_utils.py b/test/test_utils.py index 1ee4ce8..8f1c1d5 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -510,3 +510,45 @@ class TestExpandRelease(unittest.TestCase): def test_expand_unknown_name(self): result = utils.expand_release('some_branch', self.releases) self.assertEqual(None, result) + + +class TestGetFedoraReleaseState(unittest.TestCase): + + @patch('requests.get') + def test_release_exists_current(self, mock_get): + mock_rv = Mock() + mock_rv.ok = True + mock_rv.json.return_value = { + 'name': 'F30', + 'long_name': 'Fedora 30', + 'version': '30', + 'branch': 'f30', + 'state': 'current', # this item is only important for the test + } + mock_get.return_value = mock_rv + + config = Mock() + config.get.return_value = 'https://service_url/releases/F30' + + rv = utils.get_fedora_release_state(config, 'fedpkg', 'F30') + self.assertEqual(rv, 'current') + + @patch('requests.get') + def test_release_does_not_exist(self, mock_get): + mock_rv = Mock() + mock_rv.ok = False + mock_rv.status_code = 404 + mock_get.return_value = mock_rv + + config = Mock() + config.get.return_value = 'https://service_url/releases/eng-fedora-29' + + rv = utils.get_fedora_release_state(config, 'fedpkg', 'eng-fedora-29') + self.assertEqual(rv, None) + + def test_config_does_not_have_option(self): + config = Mock() + config.get.side_effect = NoOptionError('releases_service', 'fedpkg.bodhi') + six.assertRaisesRegex(self, rpkgError, r"Could not get release state for Fedora \(F30M\): " + "No option 'releases_service' in section: 'fedpkg.bodhi'.", + utils.get_fedora_release_state, config, 'fedpkg', 'F30M')