From ac95b828ec655db42e8816abaee41b2b68228dab Mon Sep 17 00:00:00 2001
From: Anton Medvedev <amedvede@redhat.com>
Date: Jun 27 2025 05:19:46 +0000
Subject: feat(cli): added new arguments for request-repo command, when monitoring option is set it checks if package exist in Anitya and if not it ask to provide new arguments


Signed-off-by: Anton Medvedev <amedvede@redhat.com>

---

diff --git a/fedpkg/cli.py b/fedpkg/cli.py
index e5ed34b..bcd8014 100644
--- a/fedpkg/cli.py
+++ b/fedpkg/cli.py
@@ -44,7 +44,8 @@ from fedpkg.utils import (assert_new_tests_repo, assert_valid_epel_package,
                           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)
+                          new_pagure_issue, sl_list_to_dict, verify_sls,
+                          does_package_exist_in_anitya)
 
 RELEASE_BRANCH_REGEX = r'^(f\d+|el\d+|eln|epel\d+|epel\d+\.\d+)$'
 LOCAL_PACKAGE_CONFIG = 'package.cfg'
@@ -330,6 +331,16 @@ class fedpkgClient(cliClient):
             Another example to request a module foo:
 
                 fedpkg request-repo --namespace modules foo
+
+            If you would like to create project in release-monitoring
+            when requesting a new dist-git repository, use the following
+            command with mandotory arguments.
+            Full list of arguments and their values is in doc:
+            https://release-monitoring.org/static/docs/user-guide.html:
+
+                fedpkg request-repo foo 1234 --create_monitoring_project \\
+                    --homepage https://example.org --backend custom \\
+                    --distribution Fedora --project_ecosystem https://example.org
         '''.format(self.name, pagure_url_parsed))
 
         request_repo_parser = self.subparsers.add_parser(
@@ -369,6 +380,21 @@ class fedpkgClient(cliClient):
             help='The package is an exception to the regular package review '
                  'process (specifically, it does not require a Bugzilla bug)')
         request_repo_parser.add_argument(
+            '--project-name', help='The package\'s project name', required=False
+        )
+        backend_choices = [
+            "BitBucket", "Cgit", "CPAN", "CRAN", "crates.io", "Debian project",
+            "Drupal6", "Drupal7", "folder", "Freshmeat", "GitHub",
+            "GitLab", "Gitea", "GNOME", "GNU Project", "Gogs",
+            "Google code", "Hackage", "Launchpad", "Maven Central", "npmjs", "Packagist",
+            "pagure", "PEAR", "PECL", "PyPI", "Rubygems", "Sourcefoge",
+            "Sourceforge (git)", "SourceHut", "Stackage", "custom",
+        ]
+        request_repo_parser.add_argument(
+            '--backend', choices=backend_choices,
+            help='The package\'s project backend', required=False
+        )
+        request_repo_parser.add_argument(
             '--no-initial-commit',
             action='store_true',
             help='Do not include an initial commit in the repository.')
@@ -999,6 +1025,8 @@ class fedpkgClient(cliClient):
             exception=self.args.exception,
             name=self.name,
             config=self.config,
+            project_name=self.args.project_name,
+            backend=self.args.backend,
             initial_commit=not self.args.no_initial_commit,
             onboard_packit=self.args.onboard_packit,
         )
@@ -1019,7 +1047,8 @@ class fedpkgClient(cliClient):
     def _request_repo(logger, repo_name, ns, description, name, config,
                       branch=None, summary=None, upstreamurl=None,
                       monitor=None, bug=None, exception=None, anongiturl=None,
-                      initial_commit=True, onboard_packit=None):
+                      project_name=None, backend=None,
+                      initial_commit=True, onboard_packit=None,):
         """ Implementation of `request_repo`.
 
         Submits a request for a new dist-git repo.
@@ -1052,6 +1081,9 @@ class fedpkgClient(cliClient):
             granted the right to waive their package review at the discretion of
             Release Engineering.  Typically takes the value of
             `self.args.exception`.
+        :param project_name: A string representing project name for Anitya
+            if It's different from package name in distribution
+        :param backend: A string representing the backend creating Anitya project:
         :param anongiturl: A string with the name of the anonymous git url.
             Typically the value of `self.cmd.anongiturl`.
         :param onboard_packit: A string, whether to generate a default
@@ -1061,7 +1093,6 @@ class fedpkgClient(cliClient):
             Typically takes the value of `self.args.onboard_packit`.
         :return: None
         """
-
         # bug is not a required parameter in the event the packager has an
         # exception, in which case, they may use the --exception flag
         # neither in case of modules, which don't require a formal review
@@ -1118,6 +1149,24 @@ class fedpkgClient(cliClient):
             }
             if not initial_commit:
                 ticket_body['initial_commit'] = False
+        if monitor != "no-monitoring":
+            if not does_package_exist_in_anitya(repo_name):
+                if backend is None or upstreamurl == '' or upstreamurl is None:
+                    raise rpkgError(
+                        "When monitoring or monitoring-with-scratch argument are set"
+                        " you also required to provide `--backend`, `--upstreamurl`"
+                        " and `--project-name` (if its different from package name) arguments"
+                        " to create project and package for Anitya."
+                    )
+            if project_name is None:
+                project_name = repo_name
+            if backend is None:
+                backend = "custom"
+            ticket_body.update({
+                'backend': backend,
+                'project_name': project_name,
+                'distribution': 'Fedora',
+            })
 
         ticket_body = json.dumps(ticket_body, indent=True)
         ticket_body = '```\n{0}\n```'.format(ticket_body)
diff --git a/fedpkg/utils.py b/fedpkg/utils.py
index 1ce9177..b997e62 100644
--- a/fedpkg/utils.py
+++ b/fedpkg/utils.py
@@ -657,3 +657,27 @@ def disable_monitoring(logger, base_url, token, repo_name, namespace, cli_name):
         raise rpkgError(base_error_msg.format(rv_error))
 
     logger.info("Monitoring of the project was sucessfully disabled.")
+
+
+def does_package_exist_in_anitya(package_name):
+    """
+    Check if a package exists in the anitya.
+    :param package_name: the name of the package
+    :return: True if the package exists in the anitya, False otherwise
+    """
+    project_endpoint = "https://release-monitoring.org/api/v2/packages/"
+    params = {
+        'name': package_name,
+        'distribution': "Fedora"
+    }
+
+    try:
+        response = requests.get(project_endpoint, params=params, timeout=3000)
+        if response.status_code == 200:
+            return True
+        elif response.status_code == 400:
+            return False
+        else:
+            return False
+    except requests.exceptions.RequestException:
+        return False
diff --git a/test/test_cli.py b/test/test_cli.py
index a89f044..4f66123 100644
--- a/test/test_cli.py
+++ b/test/test_cli.py
@@ -491,6 +491,7 @@ class TestRequestRepo(CliTestCase):
         mock_rv.ok = True
         mock_rv.json.return_value = {'issue': {'id': 2}}
         mock_request_post.return_value = mock_rv
+        fedpkg.cli.does_package_exist_in_anitya = Mock(return_value=True)
 
         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,
                    'request-repo', 'testpkg', '1441813']
@@ -499,13 +500,16 @@ class TestRequestRepo(CliTestCase):
 
         expected_issue_content = {
             'action': 'new_repo',
+            'backend': 'custom',
             'branch': 'rawhide',
             'bug_id': 1441813,
             'description': '',
+            'distribution': 'Fedora',
             'exception': False,
             'monitor': 'monitoring',
             'namespace': 'rpms',
             'onboard_packit': 'no',
+            'project_name': 'testpkg',
             'repo': 'testpkg',
             'summary': 'a description',
             'upstreamurl': ''
@@ -529,6 +533,7 @@ class TestRequestRepo(CliTestCase):
         mock_rv.ok = True
         mock_rv.json.return_value = {'issue': {'id': 2}}
         mock_request_post.return_value = mock_rv
+        fedpkg.cli.does_package_exist_in_anitya = Mock(return_value=True)
 
         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,
                    'request-repo', 'nethack', '1441813']
@@ -547,7 +552,10 @@ class TestRequestRepo(CliTestCase):
             'repo': 'nethack',
             'summary': ('A rogue-like single player dungeon exploration '
                         'game'),
-            'upstreamurl': ''
+            'upstreamurl': '',
+            'backend': 'custom',
+            'project_name': 'nethack',
+            'distribution': 'Fedora',
         }
         # Get the data that was submitted to Pagure
         post_data = mock_request_post.call_args_list[0][1]['data']
@@ -570,6 +578,7 @@ class TestRequestRepo(CliTestCase):
         mock_rv.ok = True
         mock_rv.json.return_value = {'issue': {'id': 2}}
         mock_request_post.return_value = mock_rv
+        fedpkg.cli.does_package_exist_in_anitya = Mock(return_value=True)
 
         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,
                    'request-repo', '--namespace', 'modules', 'nethack']
@@ -587,7 +596,10 @@ class TestRequestRepo(CliTestCase):
             'onboard_packit': 'no',
             'repo': 'nethack',
             'summary': (''),
-            'upstreamurl': ''
+            'upstreamurl': '',
+            'backend': 'custom',
+            'project_name': 'nethack',
+            'distribution': 'Fedora',
         }
         # Get the data that was submitted to Pagure
         post_data = mock_request_post.call_args_list[0][1]['data']
@@ -610,6 +622,7 @@ class TestRequestRepo(CliTestCase):
         mock_rv.ok = True
         mock_rv.json.return_value = {'issue': {'id': 2}}
         mock_request_post.return_value = mock_rv
+        fedpkg.cli.does_package_exist_in_anitya = Mock(return_value=True)
 
         cli_cmd = [
             'fedpkg-stage', '--path', self.cloned_repo_path,
@@ -630,7 +643,10 @@ class TestRequestRepo(CliTestCase):
             'repo': 'nethack',
             'summary': ('A rogue-like single player dungeon exploration '
                         'game'),
-            'upstreamurl': ''
+            'upstreamurl': '',
+            'backend': 'custom',
+            'project_name': 'nethack',
+            'distribution': 'Fedora',
         }
         # Get the data that was submitted to Pagure
         post_data = mock_request_post.call_args_list[0][1]['data']
@@ -691,6 +707,7 @@ class TestRequestRepo(CliTestCase):
         mock_rv.ok = True
         mock_rv.json.return_value = {'issue': {'id': 2}}
         mock_request_post.return_value = mock_rv
+        fedpkg.cli.does_package_exist_in_anitya = Mock(return_value=True)
 
         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,
                    'request-repo', '--exception', 'nethack']
@@ -708,7 +725,10 @@ class TestRequestRepo(CliTestCase):
             'onboard_packit': 'no',
             'repo': 'nethack',
             'summary': '',
-            'upstreamurl': ''
+            'upstreamurl': '',
+            'backend': 'custom',
+            'project_name': 'nethack',
+            'distribution': 'Fedora',
         }
         # Get the data that was submitted to Pagure
         post_data = mock_request_post.call_args_list[0][1]['data']
@@ -722,6 +742,33 @@ class TestRequestRepo(CliTestCase):
         # Since it is an exception, Bugzilla will not have been queried
         mock_bz.getbug.assert_not_called()
 
+    @patch('requests.post')
+    @patch('sys.stdout', new=io.StringIO())
+    def test_request_repo_monitoring_anitya_not_exist(self, mock_request_post, mock_bz):
+        """Tests a request-repo call with the monitoring flag when anitya does not exist
+        and arguments for creating not provided"""
+        self.mock_bug.summary = ('Review Request: testpkg - a description')
+        mock_bz.getbug.return_value = self.mock_bug
+        mock_rv = Mock()
+        mock_rv.ok = True
+        mock_rv.json.return_value = {'issue': {'id': 2}}
+        mock_request_post.return_value = mock_rv
+        fedpkg.cli.does_package_exist_in_anitya = Mock(return_value=False)
+
+        cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,
+                   'request-repo', 'testpkg', '1441813']
+        cli = self.get_cli(cli_cmd)
+        expected_error = (
+            "When monitoring or monitoring-with-scratch argument are set"
+            " you also required to provide `--backend`, `--upstreamurl`"
+            " and `--project-name` (if its different from package name) arguments"
+            " to create project and package for Anitya."
+        )
+        try:
+            cli.request_repo()
+        except rpkgError as error:
+            self.assertEqual(expected_error, str(error))
+
     def test_request_repo_wrong_package(self, mock_bz):
         """Tests request-repo errors when the package is wrong"""
         mock_bz.getbug.return_value = self.mock_bug
@@ -1568,6 +1615,7 @@ class TestRequestTestsRepo(CliTestCase):
 
         expected_issue_content = {
             'action': 'new_repo',
+            'backend': 'custom',
             'branch': 'main',
             'bug_id': '',
             'monitor': 'no-monitoring',
@@ -1575,7 +1623,9 @@ class TestRequestTestsRepo(CliTestCase):
             'onboard_packit': 'no',
             'repo': 'foo',
             'description': 'Some description',
-            'upstreamurl': ''
+            'distribution': 'Fedora',
+            'upstreamurl': '',
+            'project_name': 'foo',
         }
 
         # Get the data that was submitted to Pagure
diff --git a/test/test_utils.py b/test/test_utils.py
index 051e813..1ef5eff 100644
--- a/test/test_utils.py
+++ b/test/test_utils.py
@@ -662,3 +662,36 @@ class TestGetReleaseBranches(unittest.TestCase):
         result = utils.get_release_branches('https://bodhi.fedoraproject.org/releases/')
         self.assertEqual(result, expected_output,
                          msg=f"Expected branches {expected_output}, but got {result}")
+
+
+class TestDoesPackageExistInAnitya(unittest.TestCase):
+    """Test utils.does_package_exist_in_anitya"""
+
+    @patch('requests.get')
+    def test_does_package_exist_in_anitya_everything_is_okay(self, mock_get):
+        """Test when package exists in Anitya (status code 200)"""
+        expected_output = True
+        mock_response = Mock()
+        mock_response.status_code = 200
+        mock_get.return_value = mock_response
+        package_name = 'package_name'
+        rv = utils.does_package_exist_in_anitya(package_name)
+        self.assertEqual(rv, expected_output)
+
+    @patch('requests.get')
+    def test_does_package_exist_in_anitya_no_package_exist(self, mock_get):
+        """Test when package doesn't exist in Anitya (status code 400)"""
+        expected_output = False
+        mock_response = Mock()
+        mock_response.status_code = 400
+        mock_get.return_value = mock_response
+        package_name = 'package_name'
+        rv = utils.does_package_exist_in_anitya(package_name)
+        self.assertEqual(rv, expected_output)
+
+    @patch('requests.get')
+    def test_does_package_exist_in_anitya_exception_raises(self, mock_get):
+        """Test when there's a request exception"""
+        mock_get.side_effect = requests.exceptions.RequestException("RequestException")
+        result = utils.does_package_exist_in_anitya("error-package")
+        self.assertEqual(result, False)

