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.pagure.set_pagure")
@patch("toddlers.utils.fedora_account.set_fasjson") @patch("toddlers.utils.fedora_account.set_fasjson")
@patch("toddlers.utils.bugzilla_system.set_bz") @patch("toddlers.utils.bugzilla_system.set_bz")
@patch("toddlers.utils.anitya.set_anitya") def test_process_exception(self, mock_bugzilla, mock_fasjson, mock_pagure, toddler):
def test_process_exception(
self, mock_anitya, mock_bugzilla, mock_fasjson, mock_pagure, toddler
):
""" """
Assert that message toddler will be initialized correctly, if message passes Assert that message toddler will be initialized correctly, if message passes
initial processing. initial processing.
@ -183,16 +180,12 @@ class TestProcess:
) )
mock_fasjson.assert_called_with(config) mock_fasjson.assert_called_with(config)
mock_bugzilla.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() 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.pagure.set_pagure")
@patch("toddlers.utils.fedora_account.set_fasjson") @patch("toddlers.utils.fedora_account.set_fasjson")
@patch("toddlers.utils.bugzilla_system.set_bz") @patch("toddlers.utils.bugzilla_system.set_bz")
def test_process( def test_process(self, mock_bugzilla, mock_fasjson, mock_pagure, toddler):
self, mock_bugzilla, mock_fasjson, mock_pagure, mock_anitya, toddler
):
""" """
Assert that message toddler will be initialized correctly, if message passes Assert that message toddler will be initialized correctly, if message passes
initial processing. initial processing.
@ -234,15 +227,11 @@ class TestProcess:
) )
mock_fasjson.assert_called_with(config) mock_fasjson.assert_called_with(config)
mock_bugzilla.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.pagure.set_pagure")
@patch("toddlers.utils.fedora_account.set_fasjson") @patch("toddlers.utils.fedora_account.set_fasjson")
@patch("toddlers.utils.bugzilla_system.set_bz") @patch("toddlers.utils.bugzilla_system.set_bz")
def test_process_comment( def test_process_comment(self, mock_bugzilla, mock_fasjson, mock_pagure, toddler):
self, mock_bugzilla, mock_fasjson, mock_pagure, mock_anitya, toddler
):
""" """
Assert that toddler will handle comments correctly. Assert that toddler will handle comments correctly.
""" """
@ -802,7 +791,6 @@ class TestProcessNewRepo:
self.toddler = scm_request_processor.SCMRequestProcessor() self.toddler = scm_request_processor.SCMRequestProcessor()
self.toddler.pagure_io = Mock() self.toddler.pagure_io = Mock()
self.toddler.dist_git = Mock() self.toddler.dist_git = Mock()
self.toddler.anitya = Mock()
self.toddler.ping_comment = "{maintainers}" self.toddler.ping_comment = "{maintainers}"
def test_process_new_repo_missing_required_key(self): def test_process_new_repo_missing_required_key(self):
@ -812,77 +800,12 @@ class TestProcessNewRepo:
issue = { issue = {
"id": 100, "id": 100,
} }
json = { self.toddler.process_new_repo(issue, {})
"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.pagure_io.close_issue.assert_called_with( self.toddler.pagure_io.close_issue.assert_called_with(
100, 100,
namespace=scm_request_processor.PROJECT_NAMESPACE, namespace=scm_request_processor.PROJECT_NAMESPACE,
message="Invalid body, missing required field: upstreamurl", message="Invalid body, missing required field: repo",
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",
reason="Invalid", reason="Invalid",
) )
@ -899,8 +822,40 @@ class TestProcessNewRepo:
"bug_id": "123", "bug_id": "123",
"action": "new_repo", "action": "new_repo",
"sls": {"rawhide": "2050-06-01"}, "sls": {"rawhide": "2050-06-01"},
"monitor": "no-monitoring", "monitor": "monitor",
"upstreamurl": "", }
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) self.toddler.process_new_repo(issue, json)
@ -933,8 +888,7 @@ class TestProcessNewRepo:
"bug_id": "", "bug_id": "",
"action": "new_repo", "action": "new_repo",
"sls": {"rawhide": "2050-06-01"}, "sls": {"rawhide": "2050-06-01"},
"monitor": "no-monitoring", "monitor": "monitor",
"upstreamurl": "",
} }
self.toddler.dist_git.get_project.return_value = None self.toddler.dist_git.get_project.return_value = None
@ -963,8 +917,7 @@ class TestProcessNewRepo:
"bug_id": "123", "bug_id": "123",
"action": "new_repo", "action": "new_repo",
"sls": {"rawhide": "2050-06-01"}, "sls": {"rawhide": "2050-06-01"},
"monitor": "no-monitoring", "monitor": "monitor",
"upstreamurl": "",
} }
self.toddler.dist_git.get_project.return_value = None self.toddler.dist_git.get_project.return_value = None
@ -998,8 +951,7 @@ class TestProcessNewRepo:
bug_id = "123" bug_id = "123"
action = "new_repo" action = "new_repo"
sls = {"rawhide": "2050-06-01"} sls = {"rawhide": "2050-06-01"}
monitor = "no-monitoring" monitor = "monitor"
upstreamurl = ""
exception = False exception = False
json = { json = {
"repo": repo, "repo": repo,
@ -1009,7 +961,6 @@ class TestProcessNewRepo:
"action": action, "action": action,
"sls": sls, "sls": sls,
"monitor": monitor, "monitor": monitor,
"upstreamurl": upstreamurl,
"exception": exception, "exception": exception,
} }
@ -1039,8 +990,7 @@ class TestProcessNewRepo:
bug_id = "123" bug_id = "123"
action = "new_repo" action = "new_repo"
sls = {"rawhide": "2050-06-01"} sls = {"rawhide": "2050-06-01"}
monitor = "no-monitoring" monitor = "monitor"
upstreamurl = ""
exception = False exception = False
json = { json = {
"repo": repo, "repo": repo,
@ -1050,7 +1000,6 @@ class TestProcessNewRepo:
"action": action, "action": action,
"sls": sls, "sls": sls,
"monitor": monitor, "monitor": monitor,
"upstreamurl": upstreamurl,
"exception": exception, "exception": exception,
} }
@ -1071,10 +1020,7 @@ class TestProcessNewRepo:
comment=message, comment=message,
) )
@patch( def test_process_new_repo_master_branch(self):
"toddlers.plugins.scm_request_processor.SCMRequestProcessor.validate_review_bug"
)
def test_process_new_repo_master_branch(self, mock_validate_review_bug):
""" """
Assert that ticket will be closed when branch is set to master branch. Assert that ticket will be closed when branch is set to master branch.
Master branch is no longer allowed. Master branch is no longer allowed.
@ -1087,8 +1033,7 @@ class TestProcessNewRepo:
bug_id = "123" bug_id = "123"
action = "new_repo" action = "new_repo"
sls = {"rawhide": "2050-06-01"} sls = {"rawhide": "2050-06-01"}
monitor = "no-monitoring" monitor = "monitor"
upstreamurl = ""
exception = False exception = False
json = { json = {
"repo": repo, "repo": repo,
@ -1098,7 +1043,6 @@ class TestProcessNewRepo:
"action": action, "action": action,
"sls": sls, "sls": sls,
"monitor": monitor, "monitor": monitor,
"upstreamurl": upstreamurl,
"exception": exception, "exception": exception,
} }
self.toddler.dist_git.get_project.return_value = None self.toddler.dist_git.get_project.return_value = None
@ -1124,8 +1068,7 @@ class TestProcessNewRepo:
bug_id = "123" bug_id = "123"
action = "new_repo" action = "new_repo"
sls = {"rawhide": "2050-06-01"} sls = {"rawhide": "2050-06-01"}
monitor = "no-monitoring" monitor = "monitor"
upstreamurl = ""
exception = False exception = False
json = { json = {
"repo": repo, "repo": repo,
@ -1135,7 +1078,6 @@ class TestProcessNewRepo:
"action": action, "action": action,
"sls": sls, "sls": sls,
"monitor": monitor, "monitor": monitor,
"upstreamurl": upstreamurl,
"exception": exception, "exception": exception,
} }
self.toddler.process_new_repo(issue, json) self.toddler.process_new_repo(issue, json)
@ -1178,12 +1120,11 @@ class TestProcessNewRepo:
} }
repo = "repo" repo = "repo"
bug_id = "11" bug_id = ""
action = "new_repo" action = "new_repo"
sls = {branch: "2050-06-01"} sls = {branch: "2050-06-01"}
monitor = "no-monitoring" monitor = "monitor"
upstreamurl = "" exception = True
exception = False
json = { json = {
"repo": repo, "repo": repo,
"branch": branch, "branch": branch,
@ -1192,7 +1133,6 @@ class TestProcessNewRepo:
"action": action, "action": action,
"sls": sls, "sls": sls,
"monitor": monitor, "monitor": monitor,
"upstreamurl": upstreamurl,
"exception": exception, "exception": exception,
} }
dist_git_url = "https://src.fp.o" dist_git_url = "https://src.fp.o"
@ -1201,12 +1141,9 @@ class TestProcessNewRepo:
self.toddler.pagure_io.get_project_contributors.return_value = { self.toddler.pagure_io.get_project_contributors.return_value = {
"users": {"admin": [user], "commit": [], "ticket": []} "users": {"admin": [user], "commit": [], "ticket": []}
} }
self.toddler.validation_comment = "valid"
self.toddler.validate_review_bug = Mock()
# Method to test # Method to test
with patch("toddlers.plugins.scm_request_processor.bugzilla_system"): self.toddler.process_new_repo(issue, json)
self.toddler.process_new_repo(issue, json)
# asserts # asserts
self.toddler.pagure_io.add_comment_to_issue.assert_called_with( self.toddler.pagure_io.add_comment_to_issue.assert_called_with(
@ -1239,8 +1176,7 @@ class TestProcessNewRepo:
bug_id = "" bug_id = ""
action = "new_repo" action = "new_repo"
sls = {branch: "2050-06-01"} sls = {branch: "2050-06-01"}
monitor = "no-monitoring" monitor = "monitor"
upstreamurl = ""
exception = True exception = True
json = { json = {
"repo": repo, "repo": repo,
@ -1250,7 +1186,6 @@ class TestProcessNewRepo:
"action": action, "action": action,
"sls": sls, "sls": sls,
"monitor": monitor, "monitor": monitor,
"upstreamurl": upstreamurl,
"exception": exception, "exception": exception,
} }
dist_git_url = "https://src.fp.o" 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.bugzilla_system")
@patch( @patch(
"toddlers.plugins.scm_request_processor.SCMRequestProcessor._validate_new_repo_request", "toddlers.plugins.scm_request_processor.SCMRequestProcessor.validate_review_bug"
return_value=True,
) )
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 Assert that ticket will be processed correctly when repo already
exists in dist git. exists in dist git.
@ -1284,7 +1218,7 @@ class TestProcessNewRepo:
bug_id = "123" bug_id = "123"
action = "new_repo" action = "new_repo"
sls = {"rawhide": "2050-06-01"} sls = {"rawhide": "2050-06-01"}
monitor = "no-monitoring" monitor = "monitor"
exception = False exception = False
json = { json = {
"repo": repo, "repo": repo,
@ -1295,7 +1229,6 @@ class TestProcessNewRepo:
"sls": sls, "sls": sls,
"monitor": monitor, "monitor": monitor,
"exception": exception, "exception": exception,
"upstreamurl": "",
} }
dist_git_url = "https://src.fp.o" dist_git_url = "https://src.fp.o"
@ -1337,7 +1270,7 @@ class TestProcessNewRepo:
bug_id = "123" bug_id = "123"
action = "new_repo" action = "new_repo"
sls = {branch: "2050-06-01"} sls = {branch: "2050-06-01"}
monitor = "no-monitoring" monitor = "monitor"
exception = False exception = False
json = { json = {
"repo": repo, "repo": repo,
@ -1348,7 +1281,6 @@ class TestProcessNewRepo:
"sls": sls, "sls": sls,
"monitor": monitor, "monitor": monitor,
"exception": exception, "exception": exception,
"upstreamurl": "",
} }
self.toddler.branch_slas = {"rawhide": {"rawhide": "2050-06-01"}} 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) 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.bugzilla_system")
@patch( @patch(
"toddlers.plugins.scm_request_processor.SCMRequestProcessor.validate_review_bug" "toddlers.plugins.scm_request_processor.SCMRequestProcessor.validate_review_bug"
@ -1717,8 +1341,7 @@ class TestProcessNewRepo:
bug_id = "123" bug_id = "123"
action = "new_repo" action = "new_repo"
sls = {branch: "2050-06-01"} sls = {branch: "2050-06-01"}
monitor = "no-monitoring" monitor = "monitor"
upstreamurl = ""
exception = False exception = False
json = { json = {
"repo": repo, "repo": repo,
@ -1728,7 +1351,6 @@ class TestProcessNewRepo:
"action": action, "action": action,
"sls": sls, "sls": sls,
"monitor": monitor, "monitor": monitor,
"upstreamurl": upstreamurl,
"exception": exception, "exception": exception,
} }
self.toddler.branch_slas = {"rawhide": {"rawhide": "2050-06-01"}} self.toddler.branch_slas = {"rawhide": {"rawhide": "2050-06-01"}}
@ -1789,8 +1411,7 @@ class TestProcessNewRepo:
bug_id = "123" bug_id = "123"
action = "new_repo" action = "new_repo"
sls = {branch: "2050-06-01"} sls = {branch: "2050-06-01"}
monitor = "no-monitoring" monitor = "monitor"
upstreamurl = ""
exception = False exception = False
json = { json = {
"repo": repo, "repo": repo,
@ -1800,7 +1421,6 @@ class TestProcessNewRepo:
"action": action, "action": action,
"sls": sls, "sls": sls,
"monitor": monitor, "monitor": monitor,
"upstreamurl": upstreamurl,
"exception": exception, "exception": exception,
} }
@ -1853,7 +1473,7 @@ class TestProcessNewRepo:
bug_id = "123" bug_id = "123"
action = "new_repo" action = "new_repo"
sls = {branch: "2050-06-01"} sls = {branch: "2050-06-01"}
monitor = "no-monitoring" monitor = "monitor"
exception = False exception = False
json = { json = {
"repo": repo, "repo": repo,
@ -1863,7 +1483,6 @@ class TestProcessNewRepo:
"action": action, "action": action,
"sls": sls, "sls": sls,
"monitor": monitor, "monitor": monitor,
"upstreamurl": "",
"exception": exception, "exception": exception,
} }
@ -1923,7 +1542,7 @@ class TestProcessNewRepo:
bug_id = "123" bug_id = "123"
action = "new_repo" action = "new_repo"
sls = {branch: "2050-06-01"} sls = {branch: "2050-06-01"}
monitor = "no-monitoring" monitor = "monitor"
exception = False exception = False
json = { json = {
"repo": repo, "repo": repo,
@ -1933,7 +1552,6 @@ class TestProcessNewRepo:
"action": action, "action": action,
"sls": sls, "sls": sls,
"monitor": monitor, "monitor": monitor,
"upstreamurl": "",
"exception": exception, "exception": exception,
} }
@ -2009,7 +1627,7 @@ class TestProcessNewRepo:
bug_id = "123" bug_id = "123"
action = "new_repo" action = "new_repo"
sls = {branch: "2050-06-01"} sls = {branch: "2050-06-01"}
monitor = "no-monitoring" monitor = "monitor"
exception = False exception = False
json = { json = {
"repo": repo, "repo": repo,
@ -2019,7 +1637,6 @@ class TestProcessNewRepo:
"action": action, "action": action,
"sls": sls, "sls": sls,
"monitor": monitor, "monitor": monitor,
"upstreamurl": "",
"exception": exception, "exception": exception,
} }
@ -2086,7 +1703,7 @@ class TestProcessNewRepo:
bug_id = "123" bug_id = "123"
action = "new_repo" action = "new_repo"
sls = {branch: "2050-06-01"} sls = {branch: "2050-06-01"}
monitor = "no-monitoring" monitor = "monitor"
exception = False exception = False
json = { json = {
"repo": repo, "repo": repo,
@ -2096,7 +1713,6 @@ class TestProcessNewRepo:
"action": action, "action": action,
"sls": sls, "sls": sls,
"monitor": monitor, "monitor": monitor,
"upstreamurl": "",
"exception": exception, "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`. Unit tests for `toddlers.utils.git`.
""" """
from unittest.mock import MagicMock, Mock, patch from unittest.mock import call, MagicMock, Mock, patch
import pytest import pytest
@ -290,28 +290,24 @@ class TestGitRepoRevertLastCommit:
Assert that revert last commit process correctly. Assert that revert last commit process correctly.
""" """
mock_origin = MagicMock() mock_origin = MagicMock()
mock_origin.url = "https://example.com"
self.repo.repo.remote.return_value = mock_origin 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.checkout.assert_called_once_with("feature_branch")
self.repo.repo.git.revert.assert_called_once_with("HEAD", no_edit=True) self.repo.repo.git.execute.assert_has_calls(
self.repo.repo.git.commit.assert_called_once_with( [
"--amend", "-m", "Revert message" call(["git", "revert", "--no-edit", "HEAD"]),
) call(["git", "commit", "--amend", "-m", "Revert message"]),
]
mock_git_cmd.push.assert_called_once_with(
"-u", "https://bot:token@example.com", "feature_branch"
) )
mock_origin.push.assert_called_once()
def test_revert_last_commit_revert_exception(self): def test_revert_last_commit_revert_exception(self):
mock_origin = MagicMock() mock_origin = MagicMock()
self.repo.repo.remote.return_value = mock_origin 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() 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" 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 # 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" 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 # Pagure mapping to bugzilla

View file

@ -24,14 +24,7 @@ import tomllib
from toddlers.base import ToddlerBase from toddlers.base import ToddlerBase
from toddlers.exceptions import ValidationError from toddlers.exceptions import ValidationError
from toddlers.utils import ( from toddlers.utils import bugzilla_system, fedora_account, git, pagure, requests
anitya,
bugzilla_system,
fedora_account,
git,
pagure,
requests,
)
# Regex for branch name validation # Regex for branch name validation
STREAM_NAME_REGEX = r"^[a-zA-Z0-9.\-_+]+$" STREAM_NAME_REGEX = r"^[a-zA-Z0-9.\-_+]+$"
@ -107,9 +100,6 @@ class SCMRequestProcessor(ToddlerBase):
# for toddler # for toddler
pagure_user: str = "" pagure_user: str = ""
# Anitya object to work with Anitya
anitya: anitya.Anitya
def accepts_topic(self, topic: str) -> bool: def accepts_topic(self, topic: str) -> bool:
"""Returns a boolean whether this toddler is interested in messages """Returns a boolean whether this toddler is interested in messages
from this specific topic. from this specific topic.
@ -197,9 +187,6 @@ class SCMRequestProcessor(ToddlerBase):
_log.info("Setting up connection to Bugzilla") _log.info("Setting up connection to Bugzilla")
bugzilla_system.set_bz(config) bugzilla_system.set_bz(config)
_log.info("Setting up connection to Anitya")
self.anitya = anitya.set_anitya(config)
try: try:
if message.topic.endswith("pagure.issue.comment.added"): if message.topic.endswith("pagure.issue.comment.added"):
self.process_comment(issue) self.process_comment(issue)
@ -463,12 +450,6 @@ class SCMRequestProcessor(ToddlerBase):
"namespace", "namespace",
"sls", "sls",
"monitor", "monitor",
"upstreamurl",
]
required_keys_for_monitor = [
"backend",
"project_name",
"distribution",
] ]
for key in required_keys: for key in required_keys:
if key not in issue_body_json.keys(): if key not in issue_body_json.keys():
@ -480,18 +461,6 @@ class SCMRequestProcessor(ToddlerBase):
) )
return 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 # Validate the request first
if self._validate_new_repo_request(issue, issue_body_json): if self._validate_new_repo_request(issue, issue_body_json):
_log.info("Ticket passed all validations. Creating repository.") _log.info("Ticket passed all validations. Creating repository.")
@ -667,7 +636,6 @@ class SCMRequestProcessor(ToddlerBase):
branch_name = issue_body_json.get("branch", "").strip() branch_name = issue_body_json.get("branch", "").strip()
description = issue_body_json.get("description", "").strip() description = issue_body_json.get("description", "").strip()
upstreamurl = issue_body_json.get("upstreamurl", "").strip() upstreamurl = issue_body_json.get("upstreamurl", "").strip()
monitor = issue_body_json.get("monitor", "").strip()
if namespace in ["rpms", "container"]: if namespace in ["rpms", "container"]:
default_branch = "rawhide" default_branch = "rawhide"
@ -768,50 +736,6 @@ class SCMRequestProcessor(ToddlerBase):
'You may commit to the branch "{1}" in about ' 'You may commit to the branch "{1}" in about '
"10 minutes.".format(dist_git_url, branch_name) "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( self.pagure_io.close_issue(
issue["id"], issue["id"],
namespace=PROJECT_NAMESPACE, 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 except Exception: # Raised when branch name is not correct
return None return None
def revert_last_commit( def revert_last_commit(self, message: str, branch: str = "rawhide") -> None:
self, message: str, user: str, token: str, branch: str = "rawhide"
) -> None:
""" """
Revert last commit with message on requested branch. Revert last commit with message on requested branch.
@ -151,16 +149,10 @@ class GitRepo:
self.repo.git.checkout(branch) self.repo.git.checkout(branch)
# reverting last commit and changing the commit message # reverting last commit and changing the commit message
self.repo.git.revert("HEAD", no_edit=True) self.repo.git.execute(["git", "revert", "--no-edit", "HEAD"])
self.repo.git.commit("--amend", "-m", message) self.repo.git.execute(["git", "commit", "--amend", "-m", message])
origin = self.repo.remote("origin") origin = self.repo.remote("origin")
git_cmd = self.repo.git origin.push()
push_url = origin.url.replace(
"https://", "https://{0}:{1}@".format(user, token)
)
git_cmd.push("-u", push_url, branch)
except Exception as error: except Exception as error:
print(error, "\nSomething happened during reverting the last commit") print(error, "\nSomething happened during reverting the last commit")