From 7f1b8c75a718a6a54343dc2dcd5b51efefd91ea8 Mon Sep 17 00:00:00 2001 From: Ondrej Nosek Date: Feb 06 2023 23:57:26 +0000 Subject: New command `disable-monitoring` After the repository is retired, its monitoring needs to be disabled. Based also on the code from @lenkaseg: https://pagure.io/fedpkg/pull-request/497 https://pagure.io/rpkg/pull-request/646 Relates: https://pagure.io/releng/issue/10724 JIRA: RHELCMP-11025 Signed-off-by: Ondrej Nosek --- diff --git a/fedpkg/cli.py b/fedpkg/cli.py index 5dd69b6..57659af 100644 --- a/fedpkg/cli.py +++ b/fedpkg/cli.py @@ -34,8 +34,8 @@ from fedpkg.bugzilla import BugzillaClient from fedpkg.completers import (build_arches, distgit_branches, fedpkg_packages, list_targets) from fedpkg.utils import (assert_new_tests_repo, assert_valid_epel_package, - config_get_safely, do_add_remote, do_fork, - expand_release, get_dist_git_url, + config_get_safely, disable_monitoring, do_add_remote, + do_fork, expand_release, get_dist_git_url, get_fedora_release_state, get_pagure_branches, get_release_branches, get_stream_branches, is_epel, new_pagure_issue, sl_list_to_dict, verify_sls) @@ -130,6 +130,7 @@ class fedpkgClient(cliClient): self.register_override() self.register_set_distgit_token() self.register_set_pagure_token() + self.register_do_disable_monitoring() def setup_completers(self): """ @@ -721,6 +722,39 @@ class fedpkgClient(cliClient): targets to build the package for a particular stream. '''.format('\n'.join(textwrap.wrap(build_parser.description)))) + def register_do_disable_monitoring(self): + help_msg = 'Disable monitoring of the current repository' + distgit_section = '{0}.distgit'.format(self.name) + distgit_api_base_url = config_get_safely(self.config, distgit_section, "apibaseurl") + + description = textwrap.dedent(''' + Disable monitoring of the current repository + + No arguments are needed. + Before the operation, you need to generate a pagure.io API token at: + https://{1}/settings/token/new + + ACL required: + "Modify an existing project" + + Update your token with the following command: + fedpkg set-distgit-token + + Command saves token to fedpkg config file: + ~/.config/rpkg/{0}.conf + + For example: + [{0}.distgit] + token = + '''.format(self.name, urlparse(distgit_api_base_url).netloc)) + + disable_monitoring_parser = self.subparsers.add_parser( + 'disable-monitoring', + formatter_class=argparse.RawDescriptionHelpFormatter, + help=help_msg, + description=description) + disable_monitoring_parser.set_defaults(command=self.do_disable_monitoring) + # Target functions go here def _format_update_clog(self, clog): ''' Format clog for the update template. ''' @@ -1516,12 +1550,30 @@ class fedpkgClient(cliClient): # Allow retiring in epel if is_epel(self.cmd.branch_merge): super(fedpkgClient, self).retire() + self.do_disable_monitoring() else: state = get_fedora_release_state(self.config, self.name, self.cmd.branch_merge) # Allow retiring in Rawhide and Branched until Final Freeze if state is None or state == 'pending': super(fedpkgClient, self).retire() + self.do_disable_monitoring() else: self.log.error("Fedora release (%s) is in state '%s' - retire operation " "is not allowed." % (self.cmd.branch_merge, state)) + + def do_disable_monitoring(self): + """ + Disable monitoring when package is retired. + """ + distgit_section = '{0}.distgit'.format(self.name) + distgit_api_base_url = config_get_safely(self.config, distgit_section, "apibaseurl") + distgit_token = config_get_safely(self.config, distgit_section, 'token') + disable_monitoring( + logger=self.log, + base_url=distgit_api_base_url, + token=distgit_token, + repo_name=self.cmd.repo_name, + namespace=self.cmd.ns, + cli_name=self.name, + ) diff --git a/fedpkg/utils.py b/fedpkg/utils.py index 301bdab..6511002 100644 --- a/fedpkg/utils.py +++ b/fedpkg/utils.py @@ -645,3 +645,54 @@ def config_get_safely(config, section, option): raise rpkgError("{0}\n{1}".format(msg, hint)) except Exception: raise + + +def disable_monitoring(logger, base_url, token, repo_name, namespace, cli_name): + """ + Disable monitoring when package is retired. + :param logger: A logger object + :param base_url: a string of the URL repository + :param token: a string of the API token that has rights to modify the project + :param repo_name: a string of the repository name + :param namespace: a string determines a type of the repository + :param cli_name: string of the CLI's name (e.g. fedpkg) + :return: a bool; True when monitoring wa disabled, False when failed to disable + """ + pagure_anitya_status_url = '{0}/_dg/anitya/{1}/{2}'.format( + base_url.rstrip('/'), namespace, repo_name) + logger.debug('Pagure API request URL: {0}'.format(pagure_anitya_status_url)) + headers = { + 'Authorization': 'token {0}'.format(token.strip('"').strip("'")), + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + payload = json.dumps({ + 'anitya_status': 'no-monitoring', + }) + + try: + rv = requests.post( + pagure_anitya_status_url, headers=headers, data=payload, timeout=90) + except ConnectionError as error: + error_msg = ('The connection to API failed while trying to disable monitoring ' + 'of the project. The error was: {0}'.format(str(error))) + raise rpkgError(error_msg) + + try: + # Extract response error text (and debug data) + rv_json = rv.json() + logger.debug("Pagure API response: '{0}'".format(rv_json)) + rv_error = rv_json.get('error') + except (ValueError, AttributeError): + rv_error = rv.text + + base_error_msg = ('The following error occurred while disabling monitoring: {0}') + if not rv.ok: + # show hint for expired token + if re.search(r"Invalid or expired token", rv_error, re.IGNORECASE): + base_error_msg += '\nFor invalid or expired tokens please ' \ + 'set a new token in your user configuration with:' \ + '\n\n\t{0} set-distgit-token \n'.format(cli_name) + raise rpkgError(base_error_msg.format(rv_error)) + + logger.info("Monitoring of the project was sucessfully disabled.") diff --git a/test/fedpkg-test.conf b/test/fedpkg-test.conf index 6774f31..d1ce72e 100644 --- a/test/fedpkg-test.conf +++ b/test/fedpkg-test.conf @@ -25,3 +25,4 @@ url = https://pdc.example.com/ [fedpkg.distgit] apibaseurl = https://src.example.com +token = notsecretatall diff --git a/test/test_retire.py b/test/test_retire.py index ef680c4..cd55e60 100644 --- a/test/test_retire.py +++ b/test/test_retire.py @@ -5,10 +5,12 @@ import shutil import subprocess import tempfile +import six +from pyrpkg.errors import rpkgError from six.moves import configparser +from utils import unittest from fedpkg.cli import fedpkgClient -from utils import unittest try: from unittest import mock @@ -76,8 +78,14 @@ class RetireTestCase(unittest.TestCase): 'fedpkg.spec'))) self.assertEqual(self._get_latest_commit(), reason) + @mock.patch('requests.post') @mock.patch("requests.get", new=lambda *args, **kwargs: mock.Mock(status_code=404)) - def test_retire_with_namespace(self): + def test_retire_with_namespace(self, requests_post): + mock_rv = mock.Mock() + mock_rv.ok = True + mock_rv.json.return_value = {"anitya_status": "no-monitoring"} + requests_post.return_value = mock_rv + self._setup_repo('ssh://git@pkgs.example.com/rpms/fedpkg') args = ['fedpkg', '--release=rawhide', 'retire', 'my reason'] @@ -87,8 +95,52 @@ class RetireTestCase(unittest.TestCase): self.assertRetired('my reason') self.assertEqual(len(client.cmd.push.call_args_list), 1) + url = 'https://src.example.com/_dg/anitya/rpms/fedpkg' + data = '{"anitya_status": "no-monitoring"}' + headers = { + 'Authorization': 'token notsecretatall', + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + requests_post.assert_called_once_with(url, data=data, headers=headers, timeout=90) + + @mock.patch('requests.post') + @mock.patch("requests.get", new=lambda *args, **kwargs: mock.Mock(status_code=404)) + def test_retire_with_namespace_disable_monitoring_fails(self, requests_post): + """ + retire operation itself runs fine, but subsequent procedure disable-monitoring fails + """ + mock_rv = mock.Mock() + mock_rv.ok = False + mock_rv.json.return_value = {"error": "Invalid or expired token"} + requests_post.return_value = mock_rv + + self._setup_repo('ssh://git@pkgs.example.com/rpms/fedpkg') + args = ['fedpkg', '--release=rawhide', 'retire', 'my reason'] + + client = self._fake_client(args) + self.assertRaisesRegex( + rpkgError, + "The following error occurred while disabling monitoring: Invalid or expired token\n" + "For invalid or expired tokens please set a new token", + client.retire + ) + + self.assertRetired('my reason') + self.assertEqual(len(client.cmd.push.call_args_list), 1) + + url = 'https://src.example.com/_dg/anitya/rpms/fedpkg' + data = '{"anitya_status": "no-monitoring"}' + headers = { + 'Authorization': 'token notsecretatall', + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + requests_post.assert_called_once_with(url, data=data, headers=headers, timeout=90) + + @mock.patch('requests.post') @mock.patch("requests.get", new=lambda *args, **kwargs: mock.Mock(status_code=404)) - def test_retire_without_namespace(self): + def test_retire_without_namespace(self, requests_post): self._setup_repo('ssh://git@pkgs.example.com/fedpkg') args = ['fedpkg', '--release=rawhide', 'retire', 'my reason'] @@ -98,8 +150,9 @@ class RetireTestCase(unittest.TestCase): self.assertRetired('my reason') self.assertEqual(len(client.cmd.push.call_args_list), 1) + @mock.patch('requests.post') @mock.patch("requests.get", new=lambda *args, **kwargs: mock.Mock(status_code=404)) - def test_package_is_retired_already(self): + def test_package_is_retired_already(self, requests_post): self._setup_repo('ssh://git@pkgs.example.com/fedpkg') with open(os.path.join(self.tmpdir, 'dead.package'), 'w') as f: f.write('dead package')