Compare commits

..

No commits in common. "main" and "production" have entirely different histories.

9 changed files with 76 additions and 2777 deletions

View file

@ -134,10 +134,7 @@ class TestProcess:
@patch("toddlers.utils.pagure.set_pagure")
@patch("toddlers.utils.fedora_account.set_fasjson")
@patch("toddlers.utils.bugzilla_system.set_bz")
@patch("toddlers.utils.anitya.set_anitya")
def test_process_exception(
self, mock_anitya, mock_bugzilla, mock_fasjson, mock_pagure, toddler
):
def test_process_exception(self, mock_bugzilla, mock_fasjson, mock_pagure, toddler):
"""
Assert that message toddler will be initialized correctly, if message passes
initial processing.
@ -183,16 +180,12 @@ class TestProcess:
)
mock_fasjson.assert_called_with(config)
mock_bugzilla.assert_called_with(config)
mock_anitya.assert_called_with(config)
mock_pagure_io.add_comment_to_issue.assert_called_once()
@patch("toddlers.utils.anitya.set_anitya")
@patch("toddlers.utils.pagure.set_pagure")
@patch("toddlers.utils.fedora_account.set_fasjson")
@patch("toddlers.utils.bugzilla_system.set_bz")
def test_process(
self, mock_bugzilla, mock_fasjson, mock_pagure, mock_anitya, toddler
):
def test_process(self, mock_bugzilla, mock_fasjson, mock_pagure, toddler):
"""
Assert that message toddler will be initialized correctly, if message passes
initial processing.
@ -234,15 +227,11 @@ class TestProcess:
)
mock_fasjson.assert_called_with(config)
mock_bugzilla.assert_called_with(config)
mock_anitya.assert_called_with(config)
@patch("toddlers.utils.anitya.set_anitya")
@patch("toddlers.utils.pagure.set_pagure")
@patch("toddlers.utils.fedora_account.set_fasjson")
@patch("toddlers.utils.bugzilla_system.set_bz")
def test_process_comment(
self, mock_bugzilla, mock_fasjson, mock_pagure, mock_anitya, toddler
):
def test_process_comment(self, mock_bugzilla, mock_fasjson, mock_pagure, toddler):
"""
Assert that toddler will handle comments correctly.
"""
@ -802,7 +791,6 @@ class TestProcessNewRepo:
self.toddler = scm_request_processor.SCMRequestProcessor()
self.toddler.pagure_io = Mock()
self.toddler.dist_git = Mock()
self.toddler.anitya = Mock()
self.toddler.ping_comment = "{maintainers}"
def test_process_new_repo_missing_required_key(self):
@ -812,77 +800,12 @@ class TestProcessNewRepo:
issue = {
"id": 100,
}
json = {
"repo": "+a",
"branch": "rawhide",
"namespace": "rpms",
"bug_id": "123",
"action": "new_repo",
"sls": {"rawhide": "2050-06-01"},
"monitor": "monitoring",
}
self.toddler.process_new_repo(issue, json)
self.toddler.process_new_repo(issue, {})
self.toddler.pagure_io.close_issue.assert_called_with(
100,
namespace=scm_request_processor.PROJECT_NAMESPACE,
message="Invalid body, missing required field: upstreamurl",
reason="Invalid",
)
def test_process_new_repo_missing_required_key_for_monitor(self):
"""
Assert that ticket will be closed if required key for monitor
is missing in request.
"""
issue = {
"id": 100,
}
json = {
"repo": "+a",
"branch": "rawhide",
"namespace": "rpms",
"bug_id": "123",
"action": "new_repo",
"sls": {"rawhide": "2050-06-01"},
"monitor": "monitoring",
"upstreamurl": "",
"backend": "GitLab",
}
self.toddler.process_new_repo(issue, json)
self.toddler.pagure_io.close_issue.assert_called_with(
100,
namespace=scm_request_processor.PROJECT_NAMESPACE,
message="Invalid body, missing required field: project_name",
reason="Invalid",
)
def test_process_new_repo_monitor_accepts_different_options(self):
"""
Assert that ticket will be closed if required key for monitor
is missing in request.
"""
issue = {
"id": 100,
}
json = {
"repo": "+a",
"branch": "rawhide",
"namespace": "rpms",
"bug_id": "123",
"action": "new_repo",
"sls": {"rawhide": "2050-06-01"},
"monitor": "monitoring11",
"upstreamurl": "",
"backend": "GitLab",
}
self.toddler.process_new_repo(issue, json)
self.toddler.pagure_io.close_issue.assert_called_with(
100,
namespace=scm_request_processor.PROJECT_NAMESPACE,
message="Invalid body, missing required field: project_name",
message="Invalid body, missing required field: repo",
reason="Invalid",
)
@ -899,8 +822,40 @@ class TestProcessNewRepo:
"bug_id": "123",
"action": "new_repo",
"sls": {"rawhide": "2050-06-01"},
"monitor": "no-monitoring",
"upstreamurl": "",
"monitor": "monitor",
}
self.toddler.process_new_repo(issue, json)
error = (
"The repository name is invalid. It must be at least two "
"characters long with only letters, numbers, hyphens, "
"underscores, plus signs, and/or periods. Please note that "
"the project cannot start with a period or a plus sign. "
"Repository name can't be longer than 64 characters."
)
self.toddler.pagure_io.close_issue.assert_called_with(
100,
namespace=scm_request_processor.PROJECT_NAMESPACE,
message=error,
reason="Invalid",
)
def test_process_new_repo_long_repo_name(self):
"""
Assert that ticket will be closed if provided repository name is invalid.
"""
issue = {"id": 100, "user": {"name": "zlopez"}}
json = {
"repo": "".join("a" for _ in range(65)),
"branch": "rawhide",
"namespace": "rpms",
"bug_id": "123",
"action": "new_repo",
"sls": {"rawhide": "2050-06-01"},
"monitor": "monitor",
}
self.toddler.process_new_repo(issue, json)
@ -933,8 +888,7 @@ class TestProcessNewRepo:
"bug_id": "",
"action": "new_repo",
"sls": {"rawhide": "2050-06-01"},
"monitor": "no-monitoring",
"upstreamurl": "",
"monitor": "monitor",
}
self.toddler.dist_git.get_project.return_value = None
@ -963,8 +917,7 @@ class TestProcessNewRepo:
"bug_id": "123",
"action": "new_repo",
"sls": {"rawhide": "2050-06-01"},
"monitor": "no-monitoring",
"upstreamurl": "",
"monitor": "monitor",
}
self.toddler.dist_git.get_project.return_value = None
@ -998,8 +951,7 @@ class TestProcessNewRepo:
bug_id = "123"
action = "new_repo"
sls = {"rawhide": "2050-06-01"}
monitor = "no-monitoring"
upstreamurl = ""
monitor = "monitor"
exception = False
json = {
"repo": repo,
@ -1009,7 +961,6 @@ class TestProcessNewRepo:
"action": action,
"sls": sls,
"monitor": monitor,
"upstreamurl": upstreamurl,
"exception": exception,
}
@ -1039,8 +990,7 @@ class TestProcessNewRepo:
bug_id = "123"
action = "new_repo"
sls = {"rawhide": "2050-06-01"}
monitor = "no-monitoring"
upstreamurl = ""
monitor = "monitor"
exception = False
json = {
"repo": repo,
@ -1050,7 +1000,6 @@ class TestProcessNewRepo:
"action": action,
"sls": sls,
"monitor": monitor,
"upstreamurl": upstreamurl,
"exception": exception,
}
@ -1071,10 +1020,7 @@ class TestProcessNewRepo:
comment=message,
)
@patch(
"toddlers.plugins.scm_request_processor.SCMRequestProcessor.validate_review_bug"
)
def test_process_new_repo_master_branch(self, mock_validate_review_bug):
def test_process_new_repo_master_branch(self):
"""
Assert that ticket will be closed when branch is set to master branch.
Master branch is no longer allowed.
@ -1087,8 +1033,7 @@ class TestProcessNewRepo:
bug_id = "123"
action = "new_repo"
sls = {"rawhide": "2050-06-01"}
monitor = "no-monitoring"
upstreamurl = ""
monitor = "monitor"
exception = False
json = {
"repo": repo,
@ -1098,7 +1043,6 @@ class TestProcessNewRepo:
"action": action,
"sls": sls,
"monitor": monitor,
"upstreamurl": upstreamurl,
"exception": exception,
}
self.toddler.dist_git.get_project.return_value = None
@ -1124,8 +1068,7 @@ class TestProcessNewRepo:
bug_id = "123"
action = "new_repo"
sls = {"rawhide": "2050-06-01"}
monitor = "no-monitoring"
upstreamurl = ""
monitor = "monitor"
exception = False
json = {
"repo": repo,
@ -1135,7 +1078,6 @@ class TestProcessNewRepo:
"action": action,
"sls": sls,
"monitor": monitor,
"upstreamurl": upstreamurl,
"exception": exception,
}
self.toddler.process_new_repo(issue, json)
@ -1178,12 +1120,11 @@ class TestProcessNewRepo:
}
repo = "repo"
bug_id = "11"
bug_id = ""
action = "new_repo"
sls = {branch: "2050-06-01"}
monitor = "no-monitoring"
upstreamurl = ""
exception = False
monitor = "monitor"
exception = True
json = {
"repo": repo,
"branch": branch,
@ -1192,7 +1133,6 @@ class TestProcessNewRepo:
"action": action,
"sls": sls,
"monitor": monitor,
"upstreamurl": upstreamurl,
"exception": exception,
}
dist_git_url = "https://src.fp.o"
@ -1201,11 +1141,8 @@ class TestProcessNewRepo:
self.toddler.pagure_io.get_project_contributors.return_value = {
"users": {"admin": [user], "commit": [], "ticket": []}
}
self.toddler.validation_comment = "valid"
self.toddler.validate_review_bug = Mock()
# Method to test
with patch("toddlers.plugins.scm_request_processor.bugzilla_system"):
self.toddler.process_new_repo(issue, json)
# asserts
@ -1239,8 +1176,7 @@ class TestProcessNewRepo:
bug_id = ""
action = "new_repo"
sls = {branch: "2050-06-01"}
monitor = "no-monitoring"
upstreamurl = ""
monitor = "monitor"
exception = True
json = {
"repo": repo,
@ -1250,7 +1186,6 @@ class TestProcessNewRepo:
"action": action,
"sls": sls,
"monitor": monitor,
"upstreamurl": upstreamurl,
"exception": exception,
}
dist_git_url = "https://src.fp.o"
@ -1268,10 +1203,9 @@ class TestProcessNewRepo:
@patch("toddlers.plugins.scm_request_processor.bugzilla_system")
@patch(
"toddlers.plugins.scm_request_processor.SCMRequestProcessor._validate_new_repo_request",
return_value=True,
"toddlers.plugins.scm_request_processor.SCMRequestProcessor.validate_review_bug"
)
def test_process_new_repo_project_exists(self, mock_validate_request, mock_bz):
def test_process_new_repo_project_exists(self, mock_validate_review_bug, mock_bz):
"""
Assert that ticket will be processed correctly when repo already
exists in dist git.
@ -1284,7 +1218,7 @@ class TestProcessNewRepo:
bug_id = "123"
action = "new_repo"
sls = {"rawhide": "2050-06-01"}
monitor = "no-monitoring"
monitor = "monitor"
exception = False
json = {
"repo": repo,
@ -1295,7 +1229,6 @@ class TestProcessNewRepo:
"sls": sls,
"monitor": monitor,
"exception": exception,
"upstreamurl": "",
}
dist_git_url = "https://src.fp.o"
@ -1337,7 +1270,7 @@ class TestProcessNewRepo:
bug_id = "123"
action = "new_repo"
sls = {branch: "2050-06-01"}
monitor = "no-monitoring"
monitor = "monitor"
exception = False
json = {
"repo": repo,
@ -1348,7 +1281,6 @@ class TestProcessNewRepo:
"sls": sls,
"monitor": monitor,
"exception": exception,
"upstreamurl": "",
}
self.toddler.branch_slas = {"rawhide": {"rawhide": "2050-06-01"}}
@ -1390,314 +1322,6 @@ class TestProcessNewRepo:
)
mock_bz.change_bug_status.assert_called_with(bug_id, "RELEASE_PENDING", message)
@patch(
"toddlers.plugins.scm_request_processor.SCMRequestProcessor._validate_new_repo_request",
return_value=True,
)
def test_process_new_repo_monitoring_project_created_successfully_package_exist(
self,
mock_validate_request,
):
"""
Assert that ticket will be processed with correct Monitoring message
when project and package exists.
"""
# Preparation
user = "zlopez"
issue = {
"id": 100,
"user": {"name": user},
}
repo = "repo"
branch = "main"
namespace = "tests"
bug_id = ""
action = "new_repo"
sls = {branch: "2050-06-01"}
monitor = "monitoring"
upstreamurl = ""
backend = "custom"
distibution = "Fedora"
project_name = "test_project"
exception = False
json = {
"repo": repo,
"branch": branch,
"namespace": namespace,
"bug_id": bug_id,
"action": action,
"sls": sls,
"monitor": monitor,
"upstreamurl": upstreamurl,
"backend": backend,
"distribution": distibution,
"project_name": project_name,
"exception": exception,
}
dist_git_url = "https://src.fp.o"
self.toddler.dist_git._pagure_url = dist_git_url
self.toddler.dist_git.get_project.return_value = {"access_users": {"owner": []}}
anitya_project_url = "https://release-monitoring.org/project/123"
self.toddler.anitya.does_project_exists_in_anitya = Mock(
return_value=anitya_project_url
)
self.toddler.anitya.does_package_exists_in_anitya = Mock(return_value=True)
project_msg = (
"Anitya project is accessible by this link \n`{0}`\n "
"you can modify it manually.".format(anitya_project_url)
)
self.toddler.process_new_repo(issue, json)
self.toddler.dist_git.set_monitoring_status.assert_called_with(
namespace, repo, monitor
)
monitoring_msg = "\nMonitoring:\n{0}\n".format(project_msg)
message = "The Pagure repository was created at {0}/{1}/{2}{3}".format(
dist_git_url, namespace, repo, monitoring_msg
)
self.toddler.pagure_io.close_issue.assert_called_with(
100,
namespace=scm_request_processor.PROJECT_NAMESPACE,
message=message,
reason="Processed",
)
@patch(
"toddlers.plugins.scm_request_processor.SCMRequestProcessor._validate_new_repo_request",
return_value=True,
)
def test_process_new_repo_monitoring_project_was_not_created(
self,
mock_validate_request,
):
"""
Assert that ticket will be processed with correct Monitoring message
when project and package exists.
"""
# Preparation
user = "zlopez"
issue = {
"id": 100,
"user": {"name": user},
}
repo = "repo"
branch = "main"
namespace = "tests"
bug_id = ""
action = "new_repo"
sls = {branch: "2050-06-01"}
monitor = "monitoring"
upstreamurl = ""
backend = "custom"
distibution = "Fedora"
project_name = "test_project"
exception = False
json = {
"repo": repo,
"branch": branch,
"namespace": namespace,
"bug_id": bug_id,
"action": action,
"sls": sls,
"monitor": monitor,
"upstreamurl": upstreamurl,
"backend": backend,
"distribution": distibution,
"project_name": project_name,
"exception": exception,
}
dist_git_url = "https://src.fp.o"
self.toddler.dist_git._pagure_url = dist_git_url
self.toddler.dist_git.get_project.return_value = {"access_users": {"owner": []}}
self.toddler.anitya.does_project_exists_in_anitya = Mock(return_value=None)
self.toddler.anitya.create_project_in_anitya = Mock(return_value=None)
project_msg = (
"Wasn't able to create project in Anitya. "
"You can create it manually on: `https://release-monitoring.org`"
)
self.toddler.process_new_repo(issue, json)
self.toddler.dist_git.set_monitoring_status.assert_called_with(
namespace, repo, monitor
)
monitoring_msg = "\nMonitoring:\n{0}\n".format(project_msg)
message = "The Pagure repository was created at {0}/{1}/{2}{3}".format(
dist_git_url, namespace, repo, monitoring_msg
)
self.toddler.pagure_io.close_issue.assert_called_with(
100,
namespace=scm_request_processor.PROJECT_NAMESPACE,
message=message,
reason="Processed",
)
@patch(
"toddlers.plugins.scm_request_processor.SCMRequestProcessor._validate_new_repo_request",
return_value=True,
)
def test_process_new_repo_monitoring_creating_package(
self,
mock_validate_request,
):
"""
Assert that ticket will be processed with correct Monitoring message
when project and package exists.
"""
# Preparation
user = "zlopez"
issue = {
"id": 100,
"user": {"name": user},
}
repo = "repo"
branch = "main"
namespace = "tests"
bug_id = ""
action = "new_repo"
sls = {branch: "2050-06-01"}
monitor = "monitoring"
upstreamurl = ""
backend = "custom"
distibution = "Fedora"
project_name = "test_project"
exception = False
json = {
"repo": repo,
"branch": branch,
"namespace": namespace,
"bug_id": bug_id,
"action": action,
"sls": sls,
"monitor": monitor,
"upstreamurl": upstreamurl,
"backend": backend,
"distribution": distibution,
"project_name": project_name,
"exception": exception,
}
dist_git_url = "https://src.fp.o"
self.toddler.dist_git._pagure_url = dist_git_url
self.toddler.dist_git.get_project.return_value = {"access_users": {"owner": []}}
anitya_project_url = "https://release-monitoring.org/project/123"
self.toddler.anitya.does_project_exists_in_anitya = Mock(
return_value=anitya_project_url
)
self.toddler.anitya.does_package_exists_in_anitya = Mock(return_value=False)
self.toddler.anitya.create_package_in_anitya = Mock(return_value="Success")
project_msg = (
"Anitya project is accessible by this link \n`{0}`\n "
"you can modify it manually.".format(anitya_project_url)
)
package_msg = "Package was created in Anitya"
self.toddler.process_new_repo(issue, json)
self.toddler.dist_git.set_monitoring_status.assert_called_with(
namespace, repo, monitor
)
monitoring_msg = "\nMonitoring:\n{0}\n{1}".format(project_msg, package_msg)
message = "The Pagure repository was created at {0}/{1}/{2}{3}".format(
dist_git_url, namespace, repo, monitoring_msg
)
self.toddler.pagure_io.close_issue.assert_called_with(
100,
namespace=scm_request_processor.PROJECT_NAMESPACE,
message=message,
reason="Processed",
)
@patch(
"toddlers.plugins.scm_request_processor.SCMRequestProcessor._validate_new_repo_request",
return_value=True,
)
def test_process_new_repo_monitoring_creating_package_fails(
self,
mock_validate_request,
):
"""
Assert that ticket will be processed with correct Monitoring message
when project and package exists.
"""
# Preparation
user = "zlopez"
issue = {
"id": 100,
"user": {"name": user},
}
repo = "repo"
branch = "main"
namespace = "tests"
bug_id = ""
action = "new_repo"
sls = {branch: "2050-06-01"}
monitor = "monitoring"
upstreamurl = ""
backend = "custom"
distibution = "Fedora"
project_name = "test_project"
exception = False
json = {
"repo": repo,
"branch": branch,
"namespace": namespace,
"bug_id": bug_id,
"action": action,
"sls": sls,
"monitor": monitor,
"upstreamurl": upstreamurl,
"backend": backend,
"distribution": distibution,
"project_name": project_name,
"exception": exception,
}
dist_git_url = "https://src.fp.o"
self.toddler.dist_git._pagure_url = dist_git_url
self.toddler.dist_git.get_project.return_value = {"access_users": {"owner": []}}
anitya_project_url = "https://release-monitoring.org/project/123"
self.toddler.anitya.does_project_exists_in_anitya = Mock(
return_value=anitya_project_url
)
self.toddler.anitya.does_package_exists_in_anitya = Mock(return_value=False)
response_msg = "Unauthorized, access token is incorrect."
self.toddler.anitya.create_package_in_anitya = Mock(return_value=response_msg)
project_msg = (
"Anitya project is accessible by this link \n`{0}`\n "
"you can modify it manually.".format(anitya_project_url)
)
package_msg = "Package wasn't created in Anitya, reason: `{0}`.".format(
response_msg
)
self.toddler.process_new_repo(issue, json)
self.toddler.dist_git.set_monitoring_status.assert_called_with(
namespace, repo, monitor
)
monitoring_msg = "\nMonitoring:\n{0}\n{1}".format(project_msg, package_msg)
message = "The Pagure repository was created at {0}/{1}/{2}{3}".format(
dist_git_url, namespace, repo, monitoring_msg
)
self.toddler.pagure_io.close_issue.assert_called_with(
100,
namespace=scm_request_processor.PROJECT_NAMESPACE,
message=message,
reason="Processed",
)
@patch("toddlers.plugins.scm_request_processor.bugzilla_system")
@patch(
"toddlers.plugins.scm_request_processor.SCMRequestProcessor.validate_review_bug"
@ -1717,8 +1341,7 @@ class TestProcessNewRepo:
bug_id = "123"
action = "new_repo"
sls = {branch: "2050-06-01"}
monitor = "no-monitoring"
upstreamurl = ""
monitor = "monitor"
exception = False
json = {
"repo": repo,
@ -1728,7 +1351,6 @@ class TestProcessNewRepo:
"action": action,
"sls": sls,
"monitor": monitor,
"upstreamurl": upstreamurl,
"exception": exception,
}
self.toddler.branch_slas = {"rawhide": {"rawhide": "2050-06-01"}}
@ -1789,8 +1411,7 @@ class TestProcessNewRepo:
bug_id = "123"
action = "new_repo"
sls = {branch: "2050-06-01"}
monitor = "no-monitoring"
upstreamurl = ""
monitor = "monitor"
exception = False
json = {
"repo": repo,
@ -1800,7 +1421,6 @@ class TestProcessNewRepo:
"action": action,
"sls": sls,
"monitor": monitor,
"upstreamurl": upstreamurl,
"exception": exception,
}
@ -1853,7 +1473,7 @@ class TestProcessNewRepo:
bug_id = "123"
action = "new_repo"
sls = {branch: "2050-06-01"}
monitor = "no-monitoring"
monitor = "monitor"
exception = False
json = {
"repo": repo,
@ -1863,7 +1483,6 @@ class TestProcessNewRepo:
"action": action,
"sls": sls,
"monitor": monitor,
"upstreamurl": "",
"exception": exception,
}
@ -1923,7 +1542,7 @@ class TestProcessNewRepo:
bug_id = "123"
action = "new_repo"
sls = {branch: "2050-06-01"}
monitor = "no-monitoring"
monitor = "monitor"
exception = False
json = {
"repo": repo,
@ -1933,7 +1552,6 @@ class TestProcessNewRepo:
"action": action,
"sls": sls,
"monitor": monitor,
"upstreamurl": "",
"exception": exception,
}
@ -2009,7 +1627,7 @@ class TestProcessNewRepo:
bug_id = "123"
action = "new_repo"
sls = {branch: "2050-06-01"}
monitor = "no-monitoring"
monitor = "monitor"
exception = False
json = {
"repo": repo,
@ -2019,7 +1637,6 @@ class TestProcessNewRepo:
"action": action,
"sls": sls,
"monitor": monitor,
"upstreamurl": "",
"exception": exception,
}
@ -2086,7 +1703,7 @@ class TestProcessNewRepo:
bug_id = "123"
action = "new_repo"
sls = {branch: "2050-06-01"}
monitor = "no-monitoring"
monitor = "monitor"
exception = False
json = {
"repo": repo,
@ -2096,7 +1713,6 @@ class TestProcessNewRepo:
"action": action,
"sls": sls,
"monitor": monitor,
"upstreamurl": "",
"exception": exception,
}

File diff suppressed because it is too large Load diff

View file

@ -1,410 +0,0 @@
"""
Unit tests for `toddlers.utils.anitya`.
"""
from unittest.mock import Mock
import pytest
import toddlers.utils.anitya as anitya
class TestAnityaSetAnitya:
"""
Test class for `toddlers.anitya.set_anitya` function.
"""
def test_set_anitya(self):
"""
Test initialization of anitya module.
"""
config = {
"anitya_endpoint": "https://release-monitoring.org",
"anitya_access_token": "TOKEN",
}
anitya_obj = anitya.set_anitya(config)
assert anitya_obj._anitya_endpoint == config.get("anitya_endpoint")
assert anitya_obj._anitya_token == config.get("anitya_access_token")
assert anitya_obj._requests_session
def test_set_anitya_no_anitya_url(self):
"""
Test initialization of anitya module without required config value.
"""
with pytest.raises(
ValueError, match=r"No anitya endpoint found in config file"
):
anitya.set_anitya({})
def test_set_anitya_no_anitya_api_key(self):
"""
Test initialization of anitya module without required config value.
"""
with pytest.raises(
ValueError, match=r"No anitya access token found in config file"
):
config = {"anitya_endpoint": "https://anitya.io"}
anitya.set_anitya(config)
class TestAnityaDoesProjectExistInAnitya:
"""
Test class for
`toddlers.anitya.Anitya.does_project_exists_in_anitya` method.
"""
def setup_method(self):
"""
Setup method for test class.
"""
config = {
"anitya_endpoint": "https://release-monitoring.org",
"anitya_access_token": "TOKEN",
}
self.anitya_obj = anitya.set_anitya(config)
self.anitya_obj._requests_session = Mock()
self.anitya_obj.remove_trailing_slashes_from_url = Mock(
return_value="https://release-monitoring.org/api/v2/projects/"
)
def test_does_project_exists_in_anitya(self):
"""
Assert that method will return correct response about project exists in anitya.
"""
endpoint = "https://release-monitoring.org/api/v2/projects/"
project_name = "amedvede_project"
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
"items": [
{
"id": 123,
"name": project_name,
}
],
"total_items": 1,
}
params = {"name": project_name}
self.anitya_obj._requests_session.get.return_value = mock_response
result = self.anitya_obj.does_project_exists_in_anitya(project_name)
assert result == "https://release-monitoring.org/project/123"
self.anitya_obj._requests_session.get.assert_called_once_with(
endpoint, params=params
)
def test_does_project_exists_in_anitya_project_not_found(self):
"""
Assert that method will return correct response about project does not exist in anitya.
"""
endpoint = "https://release-monitoring.org/api/v2/projects/"
project_name = "amedvede_project"
mock_response = Mock()
mock_response.status_code = 404
params = {"name": project_name}
self.anitya_obj._requests_session.get.return_value = mock_response
result = self.anitya_obj.does_project_exists_in_anitya(project_name)
assert result is None
self.anitya_obj._requests_session.get.assert_called_once_with(
endpoint, params=params
)
def test_does_project_exists_in_anitya_empty_items(self):
"""Assert that method will return correct response about project not found in anitya."""
endpoint = "https://release-monitoring.org/api/v2/projects/"
project_name = "amedvede_project"
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"items": [], "total_items": 0}
params = {"name": project_name}
self.anitya_obj._requests_session.get.return_value = mock_response
result = self.anitya_obj.does_project_exists_in_anitya(project_name)
assert result is None
self.anitya_obj._requests_session.get.assert_called_once_with(
endpoint, params=params
)
def test_does_project_exists_in_anitya_wrong_structure(self):
"""
Assert that method will return correct response about project has wrong structure in anitya.
"""
endpoint = "https://release-monitoring.org/api/v2/projects/"
project_name = "amedvede_project"
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
"items": [
{
"wrong": "structure",
}
],
"total_items": 1,
}
params = {"name": project_name}
self.anitya_obj._requests_session.get.return_value = mock_response
result = self.anitya_obj.does_project_exists_in_anitya(project_name)
assert result is None
self.anitya_obj._requests_session.get.assert_called_once_with(
endpoint, params=params
)
class TestAnityaDoesPackageExistInAnitya:
"""
Test class for `toddlers.anitya.Anitya.does_package_exists_in_anitya` method.
"""
def setup_method(self):
"""
Setup method for test class.
"""
config = {
"anitya_endpoint": "https://release-monitoring.org",
"anitya_access_token": "TOKEN",
}
self.anitya_obj = anitya.set_anitya(config)
self.anitya_obj._requests_session = Mock()
self.anitya_obj.remove_trailing_slashes_from_url = Mock(
return_value="https://release-monitoring.org/api/v2/packages/"
)
@pytest.mark.parametrize(
"project_name, expected_project_name, expected_result",
[
("nice_project", "nice_project", True),
("nice_project", "bad_project", False),
],
)
def test_does_package_exists_in_anitya(
self, project_name, expected_project_name, expected_result
):
"""
Assert that method will return correct response about package exists in anitya
and his project name is same with expected.
"""
endpoint = "https://release-monitoring.org/api/v2/packages/"
package_name = "amedvede_package"
distribution = "Fedora"
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
"items": [
{
"name": package_name,
"project": project_name,
}
],
"total_items": 1,
}
params = {
"name": package_name,
"distribution": distribution,
}
self.anitya_obj._requests_session.get.return_value = mock_response
result = self.anitya_obj.does_package_exists_in_anitya(
package_name, distribution, expected_project_name
)
assert result is expected_result # package and project name the same
self.anitya_obj._requests_session.get.assert_called_once_with(
endpoint, params=params
)
def test_does_package_exists_in_anitya_not_found(self):
"""
Assert that method will return correct response when package does not exist in anitya.
"""
endpoint = "https://release-monitoring.org/api/v2/packages/"
package_name = "amedvede_package"
project_name = "different name"
distribution = "Fedora"
mock_response = Mock()
mock_response.status_code = 202
params = {
"name": package_name,
"distribution": distribution,
}
self.anitya_obj._requests_session.get.return_value = mock_response
result = self.anitya_obj.does_package_exists_in_anitya(
package_name, distribution, project_name
)
assert result is False # package and project name are different
self.anitya_obj._requests_session.get.assert_called_once_with(
endpoint, params=params
)
def test_does_package_exists_in_anitya_found_zero_items(self):
"""
Assert that method will return correct response when response code is correct,
but response does not contain items.
"""
endpoint = "https://release-monitoring.org/api/v2/packages/"
package_name = "amedvede_package"
project_name = "different name"
distribution = "Fedora"
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"items": [], "total_items": 0}
params = {
"name": package_name,
"distribution": distribution,
}
self.anitya_obj._requests_session.get.return_value = mock_response
result = self.anitya_obj.does_package_exists_in_anitya(
package_name, distribution, project_name
)
assert result is False # package and project name are different
self.anitya_obj._requests_session.get.assert_called_once_with(
endpoint, params=params
)
class TestAnityaCreateProjectInAnitya:
"""
Test class for `toddlers.anitya.Anitya.create_project_in_anitya` method.
"""
def setup_method(self):
"""
Setup method for test class.
"""
config = {
"anitya_endpoint": "https://release-monitoring.org",
"anitya_access_token": "TOKEN",
}
self.anitya_obj = anitya.set_anitya(config)
self.anitya_obj._requests_session = Mock()
self.anitya_obj.remove_trailing_slashes_from_url = Mock(
return_value="https://release-monitoring.org/api/v2/projects/"
)
def test_create_project_in_anitya_successful_creation(self):
"""
Assert that method will return correct response when project is created.
"""
endpoint = "https://release-monitoring.org/api/v2/projects/"
project_name = "project"
homepage = "https://project.com"
backend = "GitHub"
test_data = {
"name": project_name,
"homepage": homepage,
"backend": backend,
}
response_json = {"id": 123}
mock_response = Mock()
mock_response.status_code = 201
mock_response.json.return_value = response_json
self.anitya_obj._requests_session.post.return_value = mock_response
result = self.anitya_obj.create_project_in_anitya(
project_name, homepage, backend
)
assert result == "https://release-monitoring.org/project/123"
self.anitya_obj._requests_session.post.assert_called_once_with(
url=endpoint,
data=test_data,
headers={"Authorization": "token TOKEN"},
)
def test_create_project_in_anitya_fail(self):
"""
Assert that method will return correct response when project is not created.
"""
endpoint = "https://release-monitoring.org/api/v2/projects/"
project_name = "project"
homepage = "https://project.com"
backend = "GitHub"
test_data = {
"name": project_name,
"homepage": homepage,
"backend": backend,
}
mock_response = Mock()
mock_response.status_code = 400
self.anitya_obj._requests_session.post.return_value = mock_response
result = self.anitya_obj.create_project_in_anitya(
project_name, homepage, backend
)
assert result is None
self.anitya_obj._requests_session.post.assert_called_once_with(
url=endpoint,
data=test_data,
headers={"Authorization": "token TOKEN"},
)
class TestAnityaCreatePackageInAnitya:
"""
Test class for `toddlers.anitya.Anitya.create_package_in_anitya` method.
"""
def setup_method(self):
"""
Setup method for test class.
"""
config = {
"anitya_endpoint": "https://release-monitoring.org",
"anitya_access_token": "TOKEN",
}
self.anitya_obj = anitya.set_anitya(config)
self.anitya_obj._requests_session = Mock()
self.anitya_obj.remove_trailing_slashes_from_url = Mock(
return_value="https://release-monitoring.org/api/v2/packages/"
)
@pytest.mark.parametrize(
"response_code, expected_result",
[
(201, "Success"),
(400, "Bad Request, some necessary arguments were not provided."),
(401, "Unauthorized, access token is incorrect."),
(409, "Conflict, package already exists."),
(404, None),
],
)
def test_create_package_in_anitya(self, response_code, expected_result):
"""
Assert that method will return correct response when package is created.
"""
endpoint = "https://release-monitoring.org/api/v2/packages/"
package_name = "test_package"
project_name = "test_project"
distribution = "Fedora"
project_ecosystem = "https://project.com"
test_data = {
"package_name": package_name,
"project_name": project_name,
"distribution": distribution,
"project_ecosystem": project_ecosystem,
}
mock_response = Mock()
mock_response.status_code = response_code
self.anitya_obj._requests_session.post.return_value = mock_response
result = self.anitya_obj.create_package_in_anitya(
package_name, project_name, distribution, project_ecosystem
)
assert result == expected_result
self.anitya_obj._requests_session.post.assert_called_once_with(
url=endpoint,
data=test_data,
headers={"Authorization": "token TOKEN"},
)

View file

@ -2,7 +2,7 @@
Unit tests for `toddlers.utils.git`.
"""
from unittest.mock import MagicMock, Mock, patch
from unittest.mock import call, MagicMock, Mock, patch
import pytest
@ -290,28 +290,24 @@ class TestGitRepoRevertLastCommit:
Assert that revert last commit process correctly.
"""
mock_origin = MagicMock()
mock_origin.url = "https://example.com"
self.repo.repo.remote.return_value = mock_origin
mock_git_cmd = MagicMock()
self.repo.repo.git = mock_git_cmd
self.repo.revert_last_commit("Revert message", "bot", "token", "feature_branch")
self.repo.revert_last_commit("Revert message", "feature_branch")
self.repo.repo.git.checkout.assert_called_once_with("feature_branch")
self.repo.repo.git.revert.assert_called_once_with("HEAD", no_edit=True)
self.repo.repo.git.commit.assert_called_once_with(
"--amend", "-m", "Revert message"
)
mock_git_cmd.push.assert_called_once_with(
"-u", "https://bot:token@example.com", "feature_branch"
self.repo.repo.git.execute.assert_has_calls(
[
call(["git", "revert", "--no-edit", "HEAD"]),
call(["git", "commit", "--amend", "-m", "Revert message"]),
]
)
mock_origin.push.assert_called_once()
def test_revert_last_commit_revert_exception(self):
mock_origin = MagicMock()
self.repo.repo.remote.return_value = mock_origin
self.repo.repo.git.revert.side_effect = Exception("Revert error")
self.repo.repo.git.execute.side_effect = Exception("Revert error")
self.repo.revert_last_commit("Revert message", "bot", "token", "feature_branch")
self.repo.revert_last_commit("Revert message", "feature_branch")
mock_origin.push.assert_not_called()

View file

@ -253,9 +253,6 @@ monitor_choices = ['no-monitoring', 'monitoring', 'monitoring-with-scratch']
ping_comment = "This request wants to skip bugzilla validation! {maintainers} could you check if this is correct? If yes, please respond to this ticket with 'valid' comment"
# This is a OIDC token that allows pagure_user to push changes to dist git
oidc_distgit_token = "OIDC token used to push git changes using pagure_user"
# Anitya access token and endpoint for managing project in release-monitoring
anitya_access_token = "API token for Anitya"
anitya_endpoint = "https://release-monitoring.org"
# Pagure mapping to bugzilla

View file

@ -24,14 +24,7 @@ import tomllib
from toddlers.base import ToddlerBase
from toddlers.exceptions import ValidationError
from toddlers.utils import (
anitya,
bugzilla_system,
fedora_account,
git,
pagure,
requests,
)
from toddlers.utils import bugzilla_system, fedora_account, git, pagure, requests
# Regex for branch name validation
STREAM_NAME_REGEX = r"^[a-zA-Z0-9.\-_+]+$"
@ -107,9 +100,6 @@ class SCMRequestProcessor(ToddlerBase):
# for toddler
pagure_user: str = ""
# Anitya object to work with Anitya
anitya: anitya.Anitya
def accepts_topic(self, topic: str) -> bool:
"""Returns a boolean whether this toddler is interested in messages
from this specific topic.
@ -197,9 +187,6 @@ class SCMRequestProcessor(ToddlerBase):
_log.info("Setting up connection to Bugzilla")
bugzilla_system.set_bz(config)
_log.info("Setting up connection to Anitya")
self.anitya = anitya.set_anitya(config)
try:
if message.topic.endswith("pagure.issue.comment.added"):
self.process_comment(issue)
@ -463,12 +450,6 @@ class SCMRequestProcessor(ToddlerBase):
"namespace",
"sls",
"monitor",
"upstreamurl",
]
required_keys_for_monitor = [
"backend",
"project_name",
"distribution",
]
for key in required_keys:
if key not in issue_body_json.keys():
@ -480,18 +461,6 @@ class SCMRequestProcessor(ToddlerBase):
)
return
monitor = issue_body_json.get("monitor", "").strip()
if monitor != "no-monitoring":
for key in required_keys_for_monitor:
if key not in issue_body_json.keys():
self.pagure_io.close_issue(
issue["id"],
namespace=PROJECT_NAMESPACE,
message="Invalid body, missing required field: {}".format(key),
reason="Invalid",
)
return
# Validate the request first
if self._validate_new_repo_request(issue, issue_body_json):
_log.info("Ticket passed all validations. Creating repository.")
@ -667,7 +636,6 @@ class SCMRequestProcessor(ToddlerBase):
branch_name = issue_body_json.get("branch", "").strip()
description = issue_body_json.get("description", "").strip()
upstreamurl = issue_body_json.get("upstreamurl", "").strip()
monitor = issue_body_json.get("monitor", "").strip()
if namespace in ["rpms", "container"]:
default_branch = "rawhide"
@ -768,50 +736,6 @@ class SCMRequestProcessor(ToddlerBase):
'You may commit to the branch "{1}" in about '
"10 minutes.".format(dist_git_url, branch_name)
)
if monitor != "no-monitoring":
_log.info("- Checking if project {0} exists in Anitya".format(repo))
backend = issue_body_json["backend"].strip()
distribution = issue_body_json["distribution"].strip()
project_name = issue_body_json["project_name"].strip()
monitoring_message = ""
project_msg = ""
package_msg = ""
anitya_project_url = self.anitya.does_project_exists_in_anitya(project_name)
if anitya_project_url is None:
anitya_project_url = self.anitya.create_project_in_anitya(
repo, upstreamurl, backend
)
if anitya_project_url is None:
project_msg = (
"Wasn't able to create project in Anitya. "
"You can create it manually on: `https://release-monitoring.org`"
)
else:
project_msg = (
"Anitya project is accessible by this link \n`{0}`\n "
"you can modify it manually."
).format(anitya_project_url)
package_exists = self.anitya.does_package_exists_in_anitya(
repo, project_name, distribution
)
if not package_exists:
response_msg = self.anitya.create_package_in_anitya(
repo, project_name, distribution, upstreamurl
)
if response_msg != "Success":
package_msg = (
"Package wasn't created in Anitya, reason: `{0}`.".format(
response_msg
)
)
else:
package_msg = "Package was created in Anitya"
monitoring_message = project_msg + "\n" + package_msg
new_repo_comment = new_repo_comment + "\nMonitoring:\n" + monitoring_message
self.pagure_io.close_issue(
issue["id"],
namespace=PROJECT_NAMESPACE,

View file

@ -1,581 +0,0 @@
"""
This is a script to automate unretirement of package automatically, when ticket is created.
Authors: Anton Medvedev <amedvede@redhat.com>
"""
import argparse
import json
import logging
import re
import sys
import tempfile
import traceback
from typing import Optional
import arrow
from fedora_messaging.api import Message
from git import GitCommandError
import koji
from pagure_messages.issue_schema import IssueNewV1
import tomllib
from toddlers.base import ToddlerBase
from toddlers.exceptions import ValidationError
from toddlers.utils import bodhi, bugzilla_system, git, pagure, requests
# Where to look for unretire request tickets
PROJECT_NAMESPACE = "releng/fedora-scm-requests"
# Keyword that will be searched for in the issue title
UNRETIRE_KEYWORD = "unretire"
# RPM package prefix, that will be searched in the issue title
RPM_PREFIX = "rpms/"
# Forbidden keywords for commit message
FORBIDDEN_KEYWORDS_FOR_COMMIT_MESSAGE = ["legal", "license"]
# Time difference limit not getting Bugzilla url
TIME_DIFFERENCE_LIMIT = 56 # 8 weeks in days
# Package retirement process url
PACKAGE_RETIREMENT_PROCESS_URL = (
"https://docs.fedoraproject.org/en-US/package-maintainers"
"/Package_Retirement_Process/#claiming"
)
# Fedora review bugzilla flag
FEDORA_REVIEW_FLAG_NAME = "fedora-review"
# Koji hub url
KOJIHUB_URL = "https://koji.fedoraproject.org/kojihub"
_log = logging.getLogger(__name__)
class UnretirePackages(ToddlerBase):
"""
Listen for new tickets in https://pagure.io/releng/fedora-scm-requests/issues
and process then, either by unretiring a package or rejecting the ticket
"""
name: str = "unretire_packages"
amqp_topics: list = ["io.pagure.*.pagure.issue.new"]
# Path to temporary dir
temp_dir: str = ""
# Requests session
requests_session: requests.requests.Session
# Dist-git base url
dist_git_base: Optional[str] = ""
# Pagure object connected to pagure.io
pagure_io: pagure.Pagure
# Pagure user that will be creating the comments on pagure
# for toddler
pagure_user: str = ""
# Git repo object
git_repo: git.GitRepo
# Koji session object
koji_session: koji.ClientSession
# Bodhi object
bodhi: bodhi.Bodhi
# OIDC distgit token
oidc_distgit_token: str
def accepts_topic(self, topic: str) -> bool:
"""
Returns a boolean whether this toddler is interested in messages
from this specific topic.
:arg topic: Topic to check
:returns: True if topic is accepted, False otherwise
"""
if topic.startswith("io.pagure."):
if topic.endswith("pagure.issue.new"):
return True
return False
def process(
self,
config: dict,
message: Message,
) -> None:
"""
Process a given message.
:arg config: Toddlers configuration
:arg message: Message to process
"""
_log.debug(
"Processing message:\n{0}".format(json.dumps(message.body, indent=2))
)
project_name = message.body["project"]["fullname"]
if project_name != PROJECT_NAMESPACE:
_log.info(
"The message doesn't belong to project {0}. Skipping message.".format(
PROJECT_NAMESPACE
)
)
return
issue = message.body["issue"]
if issue["status"] != "Open":
_log.info(
"The issue {0} is not open. Skipping message.".format(issue["id"])
)
return
issue_title = issue["title"]
words_in_issue_title = issue_title.split()
if UNRETIRE_KEYWORD != words_in_issue_title[0].lower():
_log.info(
"The issue doesn't contain keyword '{0}' in the title '{1}'"
"".format(UNRETIRE_KEYWORD, issue_title)
)
return
_log.debug("Getting temp_folder name from config.")
self.temp_dir = config.get("temp_folder", "")
_log.debug("Creating a request session.")
self.requests_session = requests.make_session()
_log.debug("Getting dist-git url from config.")
self.dist_git_base = config.get("dist_git_url")
_log.debug("Setting up connection to Pagure")
self.pagure_io = pagure.set_pagure(config)
self.pagure_user = config.get("pagure_user", "")
_log.debug("Setting up connection to Bugzilla")
bugzilla_system.set_bz(config)
_log.debug("Setting up session with Koji")
self.koji_session = koji.ClientSession(KOJIHUB_URL)
_log.debug("Setting up bodhi session")
self.bodhi = bodhi.set_bodhi(config)
_log.debug("Getting OIDC distgit token from config.")
self.oidc_distgit_token = config.get("oidc_distgit_token", "")
try:
self.process_ticket(issue)
except BaseException:
self.pagure_io.add_comment_to_issue(
issue["id"],
namespace=PROJECT_NAMESPACE,
comment=(
"Error happened during processing:\n" "```\n" "{0}\n" "```\n"
).format(traceback.format_exc()),
)
def process_ticket(self, issue: dict) -> None:
"""
Process a single ticket
:arg issue: A dictionary containing the issue
"""
_log.info("Handling pagure releng ticket '{0}'".format(issue["full_url"]))
try:
# If a ValueError is raised, that means it isn't valid JSON
issue_body = json.loads(issue["content"].strip("`").strip("\n"))
except ValueError:
_log.info("Invalid JSON in ticket. Closing '{0}'".format(issue["full_url"]))
self.pagure_io.close_issue(
issue["id"],
namespace=PROJECT_NAMESPACE,
message="Invalid JSON provided",
reason="Invalid",
)
return
package_name = issue_body["name"]
package_ns = issue_body["type"]
maintainer_fas = issue_body["maintainer"]
package_ns = self._ns_convertor(package_ns)
package_url = "{0}/{1}/{2}.git".format(
self.dist_git_base, package_ns, package_name
)
_log.debug("Verifying that package repository actually exist.")
if not self._does_url_exist(package_url):
msg = "Package repository doesnt exist. Try to repeat request."
_log.info(msg)
self.pagure_io.close_issue(
issue["id"],
namespace=PROJECT_NAMESPACE,
message=msg,
reason="Invalid",
)
return
_log.debug("Creating temporary directory")
with tempfile.TemporaryDirectory(dir=self.temp_dir) as tmp_dir:
_log.info("Cloning repo into dir with name '{0}'".format(self.temp_dir))
try:
self.git_repo = git.clone_repo(package_url, tmp_dir)
except GitCommandError:
message = "Something went wrong during cloning git repository."
_log.info(message)
self.pagure_io.close_issue(
issue["id"],
namespace=PROJECT_NAMESPACE,
message=message,
reason="Invalid",
)
return
branches = issue_body["branches"]
_log.debug("Getting active branches")
active_branches = self.bodhi.get_active_branches()
filtered_branches = [
branch for branch in branches if branch in active_branches
]
final_list_of_branches = []
deadpackage_file_path = "dead.package"
_log.debug("Verifying that branches are actually exists.")
_log.debug(
"Verifying that branches are actually retired (have a `dead.package` file)."
)
for branch in filtered_branches:
if self.git_repo.does_branch_exist(branch):
if self.git_repo.does_branch_contains_file(
branch, deadpackage_file_path
):
final_list_of_branches.append(branch)
_log.debug("Verifying if package is ready for unretirement.")
if not self._is_package_ready_for_unretirement(
issue_id=issue["id"],
branches=final_list_of_branches,
review_bugzilla=issue_body["review_bugzilla"],
):
return
_log.debug("Reverting retire commit")
revert_commit_message = "Unretirement request: {0}".format(
issue["full_url"]
)
for branch in final_list_of_branches:
self.git_repo.revert_last_commit(
message=revert_commit_message,
user=self.pagure_user,
token=self.oidc_distgit_token,
branch=branch,
)
_log.debug("Unblocking tags on Koji.")
if self._check_tags_to_unblock(final_list_of_branches, package_name):
_log.debug("Unblocking tags in koji.")
for tag in final_list_of_branches:
try:
self.koji_session.packageListUnblock(
taginfo=tag, pkginfo=package_name
)
except koji.GenericError:
msg = "Not able to unblock `{0}` tag on koji.".format(tag)
self.pagure_io.close_issue(
issue_id=issue["id"],
namespace=PROJECT_NAMESPACE,
message=msg,
reason="Invalid",
)
return
_log.debug("Verifying package is not orphan.")
if self.pagure_io.is_project_orphaned(
namespace=package_ns, repo=package_name
):
if maintainer_fas == "":
msg = "Package is ophaned, but maintainer fas is not provided."
self.pagure_io.close_issue(
issue_id=issue["id"],
namespace=PROJECT_NAMESPACE,
message=msg,
reason="Invalid",
)
return
self.pagure_io.assign_maintainer_to_project(
namespace=package_ns,
repo=package_name,
maintainer_fas=maintainer_fas,
)
_log.info(
"Package {0} is assigned to {1}".format(
f"{package_ns}/{package_name}", maintainer_fas
)
)
return
def _is_package_ready_for_unretirement(
self, issue_id: int, branches: list, review_bugzilla: str
) -> bool:
"""
Verify that package is ready for unretirement.
:arg issue_id: An int value of issue ID.
:arg branches: A list containing branches that need to be unretired.
:arg review_bugzilla: A str contain url on bugzilla review.
:returns: Bool value whether the package was verified.
"""
try:
_log.debug("Verifying the reason of retirement.")
self._verify_package_not_retired_for_reason(branches=branches)
_log.debug("Verifying the date of retirement.")
self._verify_bugzilla_ticket(
review_bugzilla=review_bugzilla, branches=branches
)
except ValidationError as error:
self.pagure_io.close_issue(
issue_id=issue_id,
namespace=PROJECT_NAMESPACE,
message=str(error),
reason="Invalid",
)
return False
return True
def _verify_package_not_retired_for_reason(self, branches: list):
"""
Verify that commit message does not contain forbidden keywords.
Raises:
`toddler.exceptions.ValidationError`: When retirement reason wasn't verified
"""
_log.debug("Verifying that issue message doesn't contain forbidden keywords")
for branch in branches:
last_commit_message = self.git_repo.get_last_commit_message(branch)
if any(
re.search(forbidden_keyword, str(last_commit_message).lower())
for forbidden_keyword in FORBIDDEN_KEYWORDS_FOR_COMMIT_MESSAGE
):
raise ValidationError(
"Package was retired for a reason: legal or license issue."
)
def _verify_bugzilla_ticket(self, review_bugzilla, branches):
"""
Verify if last commit was made more than 8 weeks ago, need to request a bugzilla ticket.
"""
_log.debug("Verifying that retire commit was made less than 8 weeks ago.")
is_need_to_verify_bz = False
for branch in branches:
last_commit_date = self.git_repo.get_last_commit_date(branch)
if last_commit_date is None:
raise ValidationError("Couldn't get a date of the retire commit.")
else:
last_commit_date = arrow.get(last_commit_date)
current_time = arrow.utcnow()
time_diff_in_days = (current_time - last_commit_date).days
if time_diff_in_days > TIME_DIFFERENCE_LIMIT:
is_need_to_verify_bz = True
if not is_need_to_verify_bz:
return
if review_bugzilla == "":
raise ValidationError(
"Bugzilla url is missing, please add it and recreate the ticket."
)
bug_id = review_bugzilla
_log.debug("Getting the bug object from bugzilla.")
try:
bug = bugzilla_system.get_bug(bug_id)
except Exception as error:
raise ValidationError(
"The Bugzilla bug could not be verified. The following "
"error was encountered: {0}".format(str(error))
)
if bug is None:
raise ValidationError(
"Bugzilla can't get the bug by bug id, fix bugzilla url."
)
if bug.product != "Fedora":
raise ValidationError(
"The bugzilla bug is for '{0}', "
"but request should be for 'Fedora'.".format(bug.product)
)
try:
_log.info("Getting {0} flag from bug".format(FEDORA_REVIEW_FLAG_NAME))
fedora_review_flag = bug.get_flags(FEDORA_REVIEW_FLAG_NAME)
fedora_review_flag_status = fedora_review_flag[0]["status"]
if fedora_review_flag_status != "+":
raise ValidationError(
"Flag fedora-review has wrong status, need to be +"
)
except TypeError:
raise ValidationError(
"Tag fedora-review is missing on bugzilla, get it and recreate the ticket."
)
def _check_tags_to_unblock(self, tags_to_unblock: list, repo: str) -> bool:
"""
Check if at least one of the tags requested to be unblocked are really blocked.
:arg tags_to_unblock: List of branch names
:arg repo: Name of package
:returns: Bool value whether program need to unblock tags
"""
_log.debug("Verifying that tags are blocked on koji.")
try:
package_tags = self.koji_session.listTags(package=repo)
if not package_tags:
raise ValidationError("Package doesn't have tags on koji.")
tags_that_suppose_to_be_blocked = []
for tag in package_tags:
prefix = "dist-"
if tag["name"].startswith(prefix):
tag_name = tag["name"][len(prefix) :] # noqa: E203
if tag_name in tags_to_unblock:
tags_that_suppose_to_be_blocked.append(tag)
if len(tags_that_suppose_to_be_blocked) == 0:
raise ValidationError(
"Request to unblock tags that don't exist on koji."
)
return any([tag["locked"] for tag in tags_that_suppose_to_be_blocked])
except koji.GenericError:
raise ValidationError("Package doesn't exist on koji.")
def _does_url_exist(self, url: str) -> bool:
"""
Check whether url exist.
:arg url: Url that might exist
:returns: True if url exist, otherwise False
"""
try:
response = self.requests_session.get(url)
except ConnectionError:
return False
return response.status_code == 200
@staticmethod
def _ns_convertor(namespace):
ns_mapping = {
"rpm": "rpms",
"test": "tests",
"flatpak": "flatpaks",
"module": "modules",
}
namespace = ns_mapping[namespace] if namespace in ns_mapping else namespace
return namespace
def _get_arguments(args):
"""Load and parse the CLI arguments.
:arg args: Script arguments
:returns: Parsed arguments
"""
parser = argparse.ArgumentParser(
description="Processor for Unretire packages, handling tickets from '{}'".format(
PROJECT_NAMESPACE
)
)
parser.add_argument(
"ticket",
type=int,
help="Number of ticket to process",
)
parser.add_argument(
"--config",
help="Configuration file",
)
parser.add_argument(
"--debug",
action="store_const",
dest="log_level",
const=logging.DEBUG,
default=logging.INFO,
help="Enable debugging output",
)
return parser.parse_args(args)
def _setup_logging(log_level: int) -> None:
"""
Set up the logging level.
:arg log_level: Log level to set
"""
handlers = []
_log.setLevel(log_level)
# We want all messages logged at level INFO or lower to be printed to stdout
info_handler = logging.StreamHandler(stream=sys.stdout)
handlers.append(info_handler)
if log_level == logging.INFO:
# In normal operation, don't decorate messages
for handler in handlers:
handler.setFormatter(logging.Formatter("%(message)s"))
logging.basicConfig(level=log_level, handlers=handlers)
def main(args):
"""Main function"""
args = _get_arguments(args)
_setup_logging(log_level=args.log_level)
_log.info("hello i'm starting work")
config = tomllib.load(args.config)
ticket = args.ticket
pagure_io = pagure.set_pagure(config)
issue = pagure_io.get_issue(ticket, PROJECT_NAMESPACE)
# Convert issue to message
body = {"project": {"fullname": PROJECT_NAMESPACE}, "issue": issue}
message = IssueNewV1(body=body)
_log.debug("Message prepared: {}".format(message.body))
UnretirePackages().process(
config=config,
message=message,
)
if __name__ == "__main__": # pragma: no cover
try:
main(sys.argv[1:])
except KeyboardInterrupt:
pass

View file

@ -1,212 +0,0 @@
"""
This module is a wrapper for Anitya. It uses Anitya API to communicate and configure
release monitoring instance.
To work with it, you need to set it up by calling `set_anitya`.
Examples:
from utils import anitya
anitya_config = {
"anitya_endpoint": "https://release-monitoring.org/",
"anitya_access_token": "secret TOKEN",
}
anitya_obj = anitya.set_anitya(config)
anitya_obj.create_project_in_anitya("<name>", "<homepage>", "<backend>")
"""
import logging
from typing import Optional
from toddlers.utils import requests
log = logging.getLogger(__name__)
def set_anitya(config):
"""
Set the connection to the Anitya API.
Params:
config: Configuration dictionary.
"""
return Anitya(config)
class Anitya(object):
"""
Object that works with Anitya.
"""
# URL to Anitya
_anitya_endpoint: str = ""
# API TOKEN to Anitya
_anitya_access_token: Optional[str] = None
# Request Session object used for communication
_requests_session: requests.requests.Session
def __init__(self, config):
"""
Initialize the Anitya class.
Params:
config (dict): A configuration with anitya_endpoint and anitya_access_token keys.
Raises:
ValueError: If no pagure_api_key is provided.
"""
self._anitya_endpoint = config.get("anitya_endpoint", "").removesuffix("/")
if not self._anitya_endpoint:
raise ValueError("No anitya endpoint found in config file")
self._anitya_token = config.get("anitya_access_token", "")
if not self._anitya_token:
raise ValueError("No anitya access token found in config file")
self._requests_session = requests.make_session(timeout=300)
def does_project_exists_in_anitya(self, project_name: str) -> Optional[str]:
"""
Check if project exists in Anitya.
Params:
project_name (str): The name of the project.
Returns:
Optional[str]: project URL if it exists in Anitya, otherwise None.
"""
projects_params = {
"name": project_name,
}
endpoint = self._anitya_endpoint + "/api/v2/projects/"
projects_response = self._requests_session.get(endpoint, params=projects_params)
if projects_response.status_code != 200:
log.debug("Project '{0}' not found in Anitya.".format(project_name))
return None
response_json = projects_response.json()
item_count = response_json["total_items"]
if item_count == 0:
log.debug("Project '{0}' not found in Anitya.".format(project_name))
return None
try:
project_id = response_json["items"][0]["id"]
project_url = "{0}/project/{1}".format(self._anitya_endpoint, project_id)
return project_url
except (KeyError, IndexError):
return None
def does_package_exists_in_anitya(
self, package_name: str, distribution: str, project_name: str
) -> bool:
"""
Check if package exists in Anitya.
Params:
package_name (str): The name of the package.
distribution (str): The name of the distribution.
project_name (str): The name of the project.
Returns:
False if package don't exist
False if package exist but his project is different from provided project name
True if package exist and his project is correct
"""
endpoint = self._anitya_endpoint + "/api/v2/packages/"
packages_params = {
"name": package_name,
"distribution": distribution,
}
packages_response = self._requests_session.get(endpoint, params=packages_params)
if packages_response.status_code != 200:
log.info("Package '{0}' not found in Anitya.".format(package_name))
return False # Not able to find package
response_json = packages_response.json()
item_count = response_json["total_items"]
if item_count < 1:
log.info("Package '{0}' not found in Anitya.".format(package_name))
return False
package_json = response_json["items"][0]
package_project_name = package_json["project"]
if package_project_name != project_name:
return False # Expected and actual project name are different
else:
return True # Expected and actual project name are the same
def create_project_in_anitya(
self,
name: str,
homepage: str,
backend: str,
) -> Optional[str]:
"""
Create a new project in Anitya.
Params:
name (str): The name of the project.
homepage (str): The homepage of the project.
backend (str): The name of the backend.
Returns:
The project URL if successful, otherwise None.
"""
headers = {"Authorization": "token " + self._anitya_token}
endpoint = self._anitya_endpoint + "/api/v2/projects/"
payload = {
"name": name,
"homepage": homepage,
"backend": backend,
}
log.info("Creating project '{0}' in Anitya.".format(name))
response = self._requests_session.post(
url=endpoint, data=payload, headers=headers
)
if response.status_code == 201:
project_json = response.json()
project_id = project_json["id"]
project_url = "{0}/project/{1}".format(self._anitya_endpoint, project_id)
return project_url
else:
return None
def create_package_in_anitya(
self,
package_name: str,
project_name: str,
distribution: str,
project_ecosystem: str,
) -> Optional[str]:
"""
Create a new package in Anitya.
Params:
package_name (str): The name of the package.
project_name (str): The name of the project.
distribution (str): The name of the distribution.
project_ecosystem (str): The name of the ecosystem.
Returns:
Return message if status code is known, otherwise None.
"""
headers = {"Authorization": "token " + self._anitya_token}
endpoint = self._anitya_endpoint + "/api/v2/packages/"
payload = {
"package_name": package_name,
"project_name": project_name,
"distribution": distribution,
"project_ecosystem": project_ecosystem,
}
log.info("Creating package '{0}' in Anitya.".format(package_name))
response = self._requests_session.post(
url=endpoint, data=payload, headers=headers
)
if response.status_code == 400:
return "Bad Request, some necessary arguments were not provided."
elif response.status_code == 401:
return "Unauthorized, access token is incorrect."
elif response.status_code == 409:
return "Conflict, package already exists."
elif response.status_code == 201:
return "Success"
return None

View file

@ -136,9 +136,7 @@ class GitRepo:
except Exception: # Raised when branch name is not correct
return None
def revert_last_commit(
self, message: str, user: str, token: str, branch: str = "rawhide"
) -> None:
def revert_last_commit(self, message: str, branch: str = "rawhide") -> None:
"""
Revert last commit with message on requested branch.
@ -151,16 +149,10 @@ class GitRepo:
self.repo.git.checkout(branch)
# reverting last commit and changing the commit message
self.repo.git.revert("HEAD", no_edit=True)
self.repo.git.commit("--amend", "-m", message)
self.repo.git.execute(["git", "revert", "--no-edit", "HEAD"])
self.repo.git.execute(["git", "commit", "--amend", "-m", message])
origin = self.repo.remote("origin")
git_cmd = self.repo.git
push_url = origin.url.replace(
"https://", "https://{0}:{1}@".format(user, token)
)
git_cmd.push("-u", push_url, branch)
origin.push()
except Exception as error:
print(error, "\nSomething happened during reverting the last commit")