| |
@@ -0,0 +1,780 @@
|
| |
+ """
|
| |
+ 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 sys
|
| |
+ import re
|
| |
+ import datetime
|
| |
+ import tempfile
|
| |
+ from typing import Optional
|
| |
+ from git import GitCommandError
|
| |
+
|
| |
+ import toml
|
| |
+ import requests
|
| |
+ import koji
|
| |
+
|
| |
+ from fedora_messaging.api import Message
|
| |
+ from pagure_messages.issue_schema import IssueNewV1
|
| |
+
|
| |
+ from toddlers.base import ToddlerBase
|
| |
+ from toddlers.exceptions import ValidationError
|
| |
+ from toddlers.utils import pagure, git, bugzilla_system, pdc
|
| |
+
|
| |
+ # Regex for bugzilla url
|
| |
+ BUGZILLA_URL_REGEX = r"https:\/\/bugzilla\.redhat\.com(.+)(id=\d{7})"
|
| |
+ # Regex for branches
|
| |
+ BRANCHES_REGEX = r"f\d{2}|rawhide"
|
| |
+ # Where to look for pdc_unretire tickets
|
| |
+ PROJECT_NAMESPACE = "releng"
|
| |
+ # Keyword that will be searched for in the issue title
|
| |
+ UNRETIRE_KEYWORD = "unretire"
|
| |
+ # 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 tag
|
| |
+ FEDORA_REVIEW_TAG_NAME = "fedora-review"
|
| |
+ # Koji hub url
|
| |
+ KOJIHUB_URL = "https://koji.fedoraproject.org/kojihub"
|
| |
+ # Set certain parts not ot run if it's not in production
|
| |
+ DEVEL = False
|
| |
+
|
| |
+ _log = logging.getLogger(__name__)
|
| |
+
|
| |
+
|
| |
+ class PDCUnretirePackages(ToddlerBase):
|
| |
+ """
|
| |
+ Listen for new tickets in https://pagure.io/releng/issues
|
| |
+ and process then, either by unretiring a package or rejecting the ticket
|
| |
+ """
|
| |
+
|
| |
+ name: str = "pdc_unretire_packages"
|
| |
+
|
| |
+ amqp_topics: dict = ["io.pagure.prod.pagure.issue.new"]
|
| |
+
|
| |
+ # Dist-git base url
|
| |
+ dist_git_base: str = ""
|
| |
+
|
| |
+ # TODO probably not working with mypy
|
| |
+ # PDC client object connected to pdc
|
| |
+ pdc_client: pdc.PDCClient
|
| |
+
|
| |
+ # Pagure object connected to pagure.io
|
| |
+ pagure_io: pagure.Pagure
|
| |
+
|
| |
+ # Pagure object conected to dist-git
|
| |
+ # dist_git: pagure.Pagure
|
| |
+
|
| |
+ # Git repo object
|
| |
+ git_repo: git.GitRepo
|
| |
+
|
| |
+ # Koji session object
|
| |
+ koji_session: koji.ClientSession
|
| |
+
|
| |
+ 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
|
| |
+ """
|
| |
+ return topic == "io.pagure.prod.pagure.issue.new"
|
| |
+
|
| |
+ 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
|
| |
+
|
| |
+ self.dist_git_base = config.get("dist_git_url")
|
| |
+
|
| |
+ issue_title = issue["title"]
|
| |
+
|
| |
+ if UNRETIRE_KEYWORD not in issue_title.lower():
|
| |
+ _log.info(
|
| |
+ "The issue doesn't contain keyword '{0}' in the title '{1}'".format(
|
| |
+ UNRETIRE_KEYWORD, issue_title
|
| |
+ )
|
| |
+ )
|
| |
+ return
|
| |
+
|
| |
+ _log.info("Setting up connection to PDC")
|
| |
+ self.pdc_client = pdc.pdc_client_for_config(config)
|
| |
+
|
| |
+ _log.info("Setting up connection to Pagure")
|
| |
+ self.pagure_io = pagure.set_pagure(config)
|
| |
+
|
| |
+ # _log.info("Setting up connection to Dist-Git")
|
| |
+ # dist_git_config = {
|
| |
+ # "pagure_url": config.get("dist_git_url"),
|
| |
+ # "pagure_api_key": config.get("dist_git_token")
|
| |
+ # }
|
| |
+ # self.dist_git = pagure.set_pagure(dist_git_config)
|
| |
+
|
| |
+ _log.info("Setting up connection to Bugzilla")
|
| |
+ bugzilla_system.set_bz(config)
|
| |
+
|
| |
+ _log.info("Setting up session with Koji")
|
| |
+ self.koji_session = koji.ClientSession(KOJIHUB_URL)
|
| |
+
|
| |
+ self.process_ticket(issue)
|
| |
+
|
| |
+ 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"]))
|
| |
+
|
| |
+ issue_title = issue["title"]
|
| |
+ issue_body = issue["content"].strip("`").strip("\n")
|
| |
+ issue_url = issue["full_url"]
|
| |
+ issue_opener = issue["user"]["name"]
|
| |
+ issue_id = issue["id"]
|
| |
+
|
| |
+ # improve searching for package name
|
| |
+ end_of_keyword_in_title_index = (
|
| |
+ issue_title.lower().index(UNRETIRE_KEYWORD) + len(UNRETIRE_KEYWORD) + 1
|
| |
+ )
|
| |
+
|
| |
+ inaccurate_package_name = issue_title[end_of_keyword_in_title_index:]
|
| |
+ list_of_words_in_package_name = [
|
| |
+ word.strip() for word in inaccurate_package_name.split(" ")
|
| |
+ ]
|
| |
+
|
| |
+ if len(list_of_words_in_package_name) > 1:
|
| |
+ _log.info(
|
| |
+ "Package name '{0}' has two or more words".format(
|
| |
+ inaccurate_package_name
|
| |
+ )
|
| |
+ )
|
| |
+ self.pagure_io.close_issue(
|
| |
+ issue_id,
|
| |
+ namespace=PROJECT_NAMESPACE,
|
| |
+ message="Invalid package name",
|
| |
+ reason="Invalid",
|
| |
+ )
|
| |
+ return
|
| |
+
|
| |
+ inaccurate_package_name = list_of_words_in_package_name[0]
|
| |
+ cloned_repo_name = "{0}_repo".format(inaccurate_package_name.replace("/", "_"))
|
| |
+ package_url = self._get_package_url(inaccurate_package_name)
|
| |
+
|
| |
+ if not package_url:
|
| |
+ _log.info(
|
| |
+ "Package with this name '{0}' wasn't found on dist-git".format(
|
| |
+ inaccurate_package_name
|
| |
+ )
|
| |
+ )
|
| |
+ self.pagure_io.close_issue(
|
| |
+ issue_id,
|
| |
+ namespace=PROJECT_NAMESPACE,
|
| |
+ message="Package wasn't found on dist-git",
|
| |
+ reason="Invalid",
|
| |
+ )
|
| |
+ return
|
| |
+
|
| |
+ namespace, repo = self._get_namespace_and_repo(package_url)
|
| |
+
|
| |
+ _log.info("Creating temporary directory")
|
| |
+ with tempfile.TemporaryDirectory():
|
| |
+ _log.info("Cloning repo into dir with name '{0}'".format(cloned_repo_name))
|
| |
+ try:
|
| |
+ self.git_repo = git.clone_repo(package_url, cloned_repo_name)
|
| |
+ except GitCommandError:
|
| |
+ message = "Remote repository doesn't exist or you do not have the appropriate " \
|
| |
+ "permissions to access it."
|
| |
+ _log.info(message)
|
| |
+ self.pagure_io.close_issue(
|
| |
+ issue_id,
|
| |
+ namespace=PROJECT_NAMESPACE,
|
| |
+ message=message,
|
| |
+ reason="Invalid",
|
| |
+ )
|
| |
+ return
|
| |
+
|
| |
+ tags_to_unblock = self._verify_package_ready_for_unretirement(
|
| |
+ issue_id=issue_id, issue_body=issue_body
|
| |
+ )
|
| |
+
|
| |
+ _log.info("Reverting retire commit")
|
| |
+ revert_commit_message = "Unretirement request: {0}".format(issue_url)
|
| |
+ self.git_repo.revert_last_commit(message=revert_commit_message)
|
| |
+
|
| |
+ _log.info("Unblocking tags on Koji.")
|
| |
+ if self._is_need_to_unblock_tags_on_koji(tags_to_unblock, repo):
|
| |
+ self._unblock_tags_on_koji(issue_id, tags_to_unblock, repo)
|
| |
+
|
| |
+ _log.info("Verifying package is not orphan.")
|
| |
+ if self.pagure_io.is_project_orphaned(namespace=namespace, repo=repo):
|
| |
+ self.pagure_io.assign_maintainer_to_project(
|
| |
+ namespace=namespace, repo=repo, maintainer_fas=issue_opener
|
| |
+ )
|
| |
+
|
| |
+ _log.info("Adjusting eol on PDC.")
|
| |
+ self._adjust_eol_pdc(namespace, repo, tags_to_unblock)
|
| |
+
|
| |
+ _log.info("everything was alright!")
|
| |
+ return
|
| |
+
|
| |
+ def _get_package_url(self, package_name: str) -> Optional[str]:
|
| |
+ """
|
| |
+ Return a package url.
|
| |
+
|
| |
+ :arg package_name: A package name with or without namespace
|
| |
+
|
| |
+ :returns: Package dist-git url or 'not_found' if url doesn't exist.
|
| |
+ """
|
| |
+ package_namespaces = ("rpms/", "modules/", "container/")
|
| |
+
|
| |
+ if package_name.startswith(package_namespaces):
|
| |
+ url = f"{self.dist_git_base}/{package_name}.git"
|
| |
+ if self._is_url_exist(url):
|
| |
+ return url
|
| |
+ else:
|
| |
+ for namespace in package_namespaces:
|
| |
+ url = f"{self.dist_git_base}/{namespace}/{package_name}.git"
|
| |
+ if self._is_url_exist(url):
|
| |
+ return url
|
| |
+ return
|
| |
+
|
| |
+ @staticmethod
|
| |
+ def _get_namespace_and_repo(package_url: str) -> Optional[tuple]:
|
| |
+ """
|
| |
+ Get namespace and package name from package url of exist package.
|
| |
+
|
| |
+ :arg package_url: A package url
|
| |
+
|
| |
+ :returns: A tuple with package namespace and package name
|
| |
+ """
|
| |
+ suffix = ".git"
|
| |
+ if package_url.endswith(suffix):
|
| |
+ url_without_suffix = package_url[: len(package_url) - len(suffix)]
|
| |
+ else:
|
| |
+ url_without_suffix = package_url
|
| |
+
|
| |
+ list_of_elements = url_without_suffix.split("/")
|
| |
+ list_of_elements = [el for el in list_of_elements if el != ""]
|
| |
+
|
| |
+ if len(list_of_elements) == 4:
|
| |
+ package_name = list_of_elements[-1]
|
| |
+ namespace = list_of_elements[-2]
|
| |
+ return namespace, package_name
|
| |
+ else:
|
| |
+ _log.info("Something wrong with url '{0}'".format(package_url))
|
| |
+ return None, None
|
| |
+
|
| |
+ def _verify_package_ready_for_unretirement(
|
| |
+ self, issue_id: int, issue_body: str
|
| |
+ ) -> list:
|
| |
+ """
|
| |
+ Verify that package is ready for unretirement.
|
| |
+
|
| |
+ :arg issue_id: AN int value of issue ID.
|
| |
+ :arg issue_body: A str contain body of issue.
|
| |
+
|
| |
+ :returns: A list with tags requester ask to unblock.
|
| |
+ """
|
| |
+ _log.info("Verifying that package is ready for unretirement")
|
| |
+
|
| |
+ if not self._is_package_not_retired_for_reason(issue_id=issue_id):
|
| |
+ raise ValidationError(
|
| |
+ "Package was retired for a reason: legal of license issue."
|
| |
+ )
|
| |
+
|
| |
+ if not self._is_last_commit_date_correct(issue_id=issue_id):
|
| |
+ raise ValidationError("Last commit was made more than 8 weeks ago.")
|
| |
+
|
| |
+ if not self._verify_bugzilla_ticket(issue_id, issue_body):
|
| |
+ raise ValidationError(
|
| |
+ "Package on bugzilla was not verified to be unretired."
|
| |
+ )
|
| |
+
|
| |
+ tags_to_unblock = self._get_tags_to_unblock(issue_body)
|
| |
+ return tags_to_unblock
|
| |
+
|
| |
+ def _is_package_not_retired_for_reason(self, issue_id: int) -> bool:
|
| |
+ """
|
| |
+ Verify that commit message does not contain forbidden keywords
|
| |
+
|
| |
+ :arg issue_id: An int value of issue ID.
|
| |
+
|
| |
+ :returns: Bool value whether package have legal issues
|
| |
+ """
|
| |
+ last_commit_message = self.git_repo.get_last_commit_message()
|
| |
+
|
| |
+ _log.info("Verifying that issue message doesn't contain forbidden keywords")
|
| |
+ if any(
|
| |
+ re.search(forbidden_keyword, last_commit_message.lower())
|
| |
+ for forbidden_keyword in FORBIDDEN_KEYWORDS_FOR_COMMIT_MESSAGE
|
| |
+ ):
|
| |
+ _log.info(
|
| |
+ "Commit message '{0}' contain forbidden keyword".format(
|
| |
+ last_commit_message
|
| |
+ )
|
| |
+ )
|
| |
+
|
| |
+ message = "Package was retire because of legal or license issue. Commit message '{0}'" \
|
| |
+ "".format(last_commit_message)
|
| |
+
|
| |
+ if not DEVEL:
|
| |
+ self.pagure_io.close_issue(
|
| |
+ issue_id=issue_id,
|
| |
+ namespace=PROJECT_NAMESPACE,
|
| |
+ message=message,
|
| |
+ reason="Closed",
|
| |
+ )
|
| |
+ return False
|
| |
+ else:
|
| |
+ return True
|
| |
+
|
| |
+ def _is_last_commit_date_correct(self, issue_id: int) -> bool:
|
| |
+ """
|
| |
+ Verify that last commit was made less than 8 weeks ago.
|
| |
+
|
| |
+ :arg issue_id: An int value of issue ID.
|
| |
+
|
| |
+ :returns: A bool value whether last commit was made more than 8 weeks ago
|
| |
+ """
|
| |
+ last_commit_date = self.git_repo.get_last_commit_date()
|
| |
+ last_commit_date_formatted = datetime.datetime.fromtimestamp(last_commit_date)
|
| |
+ current_time = datetime.datetime.now()
|
| |
+
|
| |
+ time_diff = current_time - last_commit_date_formatted
|
| |
+ time_diff_in_days = time_diff.days
|
| |
+
|
| |
+ if time_diff_in_days <= TIME_DIFFERENCE_LIMIT:
|
| |
+ return True
|
| |
+ else:
|
| |
+ _log.info(
|
| |
+ "Last commit date is more then 8 weeks (56 days) it's actually {0}".format(
|
| |
+ time_diff_in_days
|
| |
+ )
|
| |
+ )
|
| |
+
|
| |
+ message = "Package can not be unretired, because retire commit was made more than 8 " \
|
| |
+ "weeks ago."
|
| |
+
|
| |
+ if not DEVEL:
|
| |
+ self.pagure_io.close_issue(
|
| |
+ issue_id=issue_id,
|
| |
+ namespace=PROJECT_NAMESPACE,
|
| |
+ message=message,
|
| |
+ reason="Closed",
|
| |
+ )
|
| |
+ return False
|
| |
+
|
| |
+ def _verify_bugzilla_ticket(self, issue_id: int, issue_body: str) -> bool:
|
| |
+ """
|
| |
+ Check whether package exist on bugzilla and whether it has a tags
|
| |
+
|
| |
+ :arg issue_id: Int value of issue id
|
| |
+ :arg issue_body: Str with body of issue
|
| |
+
|
| |
+ :returns: A bool value whether check succeed
|
| |
+ """
|
| |
+ _log.info("Getting bugzilla url from issue body")
|
| |
+
|
| |
+ match = re.search(BUGZILLA_URL_REGEX, issue_body)
|
| |
+ if match:
|
| |
+ bugzilla_url = match.group()
|
| |
+ else:
|
| |
+ bugzilla_url = None
|
| |
+
|
| |
+ if not bugzilla_url:
|
| |
+ _log.info("Issue body doesn't contain a bugzilla url")
|
| |
+
|
| |
+ comment = (
|
| |
+ "The package is retired for more than 8 Weeks as per {0}\n"
|
| |
+ "We require a re-review to unretire this package.\n\n"
|
| |
+ "Once you have BZ with fedora_review+ please reopen this ticket."
|
| |
+ "".format(PACKAGE_RETIREMENT_PROCESS_URL)
|
| |
+ )
|
| |
+
|
| |
+ if not DEVEL:
|
| |
+ self.pagure_io.add_comment_to_issue(
|
| |
+ issue_id=issue_id,
|
| |
+ namespace=PROJECT_NAMESPACE,
|
| |
+ comment=comment,
|
| |
+ )
|
| |
+
|
| |
+ _log.info("Closing issue, because bugzilla url wasn't found")
|
| |
+
|
| |
+ message = "Bugzilla url is missing, please add it and reopen ticket."
|
| |
+
|
| |
+ if not DEVEL:
|
| |
+ self.pagure_io.close_issue(
|
| |
+ issue_id=issue_id,
|
| |
+ namespace=PROJECT_NAMESPACE,
|
| |
+ message=message,
|
| |
+ reason="Get back later",
|
| |
+ )
|
| |
+ return False
|
| |
+
|
| |
+ bug_id = bugzilla_url.split("id=")[1]
|
| |
+ try:
|
| |
+ _log.info("Getting bug with this id '{0}' data".format(bug_id))
|
| |
+
|
| |
+ bug = bugzilla_system.get_bug(bug_id)
|
| |
+ except Exception as error:
|
| |
+ message = "Bugzilla can't get the bug by bug id, fix bugzilla url."
|
| |
+ if not DEVEL:
|
| |
+ self.pagure_io.close_issue(
|
| |
+ issue_id=issue_id,
|
| |
+ namespace=PROJECT_NAMESPACE,
|
| |
+ message=message,
|
| |
+ reason="Get back later",
|
| |
+ )
|
| |
+ raise ValidationError(
|
| |
+ "The Bugzilla bug could not be verified. The following "
|
| |
+ "error was encountered: {0}".format(str(error))
|
| |
+ )
|
| |
+
|
| |
+ try:
|
| |
+ _log.info("Getting {0} tag from bug".format(FEDORA_REVIEW_TAG_NAME))
|
| |
+
|
| |
+ fedora_review_tag = bug.get_flags(FEDORA_REVIEW_TAG_NAME)
|
| |
+ fedora_review_tag_status = fedora_review_tag[0]["status"]
|
| |
+
|
| |
+ if fedora_review_tag_status != "+":
|
| |
+ message = (
|
| |
+ "Bug tag '{0}' don't have required status '{1}', "
|
| |
+ "but have '{2}' instead.\n"
|
| |
+ "Closing ticket".format(
|
| |
+ FEDORA_REVIEW_TAG_NAME, "+", fedora_review_tag_status
|
| |
+ )
|
| |
+ )
|
| |
+ _log.info(message)
|
| |
+
|
| |
+ if not DEVEL:
|
| |
+ self.pagure_io.close_issue(
|
| |
+ issue_id=issue_id,
|
| |
+ namespace=PROJECT_NAMESPACE,
|
| |
+ message=message,
|
| |
+ reason="Closed",
|
| |
+ )
|
| |
+ return False
|
| |
+
|
| |
+ except TypeError:
|
| |
+ message = "Tag fedora-review is missing on bugzilla, get it and reopen ticket later."
|
| |
+ if not DEVEL:
|
| |
+ self.pagure_io.close_issue(
|
| |
+ issue_id=issue_id,
|
| |
+ namespace=PROJECT_NAMESPACE,
|
| |
+ message=message,
|
| |
+ reason="Get back later",
|
| |
+ )
|
| |
+ raise ValidationError(
|
| |
+ "`{0}` tag is missing.".format(FEDORA_REVIEW_TAG_NAME)
|
| |
+ )
|
| |
+
|
| |
+ return True
|
| |
+
|
| |
+ def _get_tags_to_unblock(self, issue_body: str) -> list:
|
| |
+ """
|
| |
+ Get tags requester would like to unblock.
|
| |
+
|
| |
+ :arg issue_body: A str value with text of issue body
|
| |
+
|
| |
+ :returns: A list with tags requester would like to unblock
|
| |
+ """
|
| |
+ tags = re.findall(BRANCHES_REGEX, issue_body)
|
| |
+ if len(tags) == 0:
|
| |
+ tags = ["rawhide"]
|
| |
+
|
| |
+ last_branch_name = self._get_last_fedora_version_branch_name()
|
| |
+
|
| |
+ tags_to_unblock = list(
|
| |
+ map(lambda el: el.replace("rawhide", last_branch_name), tags)
|
| |
+ )
|
| |
+
|
| |
+ return tags_to_unblock
|
| |
+
|
| |
+ @staticmethod
|
| |
+ def _get_last_fedora_version_branch_name() -> str:
|
| |
+ """
|
| |
+ Get last fedora version branch name.
|
| |
+
|
| |
+ :returns: String with name
|
| |
+ """
|
| |
+ bodhi_url = "https://bodhi.fedoraproject.org/releases/"
|
| |
+ response = requests.get(url=bodhi_url)
|
| |
+ count_of_version_on_bodhi = int(response.json()["total"])
|
| |
+
|
| |
+ params = {"rows_per_page": count_of_version_on_bodhi}
|
| |
+ response = requests.get(url=bodhi_url, params=params)
|
| |
+
|
| |
+ versions = response.json()["releases"]
|
| |
+ last_version = [
|
| |
+ version
|
| |
+ for version in versions
|
| |
+ if version["state"] == "pending" and version["branch"] == "rawhide"
|
| |
+ ]
|
| |
+ if len(last_version) != 1:
|
| |
+ raise ValidationError("Not able to find a rawhide branch.")
|
| |
+
|
| |
+ last_version_name = last_version[0]["dist_tag"]
|
| |
+ return last_version_name
|
| |
+
|
| |
+ def _is_need_to_unblock_tags_on_koji(
|
| |
+ self, tags_to_unblock: list, repo: str
|
| |
+ ) -> bool:
|
| |
+ """
|
| |
+ Check if at least any 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.info("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):]
|
| |
+ 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 are not 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 _unblock_tags_on_koji(
|
| |
+ self, issue_id: int, tags_to_unblock: list, repo: str
|
| |
+ ) -> None:
|
| |
+ """
|
| |
+ Unblock certain tags for package.
|
| |
+
|
| |
+ :arg issue_id: Int value of issue id.
|
| |
+ :arg tags_to_unblock: List of tags to be unblocked
|
| |
+ :arg package_name: Package name without namespace
|
| |
+ """
|
| |
+ _log.info("Unblocking tags in koji.")
|
| |
+ if not DEVEL:
|
| |
+ for tag in tags_to_unblock:
|
| |
+ try:
|
| |
+ self.koji_session.packageListUnblock(taginfo=tag, pkginfo=repo)
|
| |
+ except koji.GenericError:
|
| |
+ comment = "Not able to unblock `{0}` tag on koji.".format(tag)
|
| |
+ self.pagure_io.add_comment_to_issue(
|
| |
+ issue_id=issue_id,
|
| |
+ namespace=PROJECT_NAMESPACE,
|
| |
+ comment=comment,
|
| |
+ )
|
| |
+
|
| |
+ def _adjust_eol_pdc(self, namespace, repo, branches: list):
|
| |
+ """
|
| |
+ Adjusting eol of requested branches.
|
| |
+
|
| |
+ Params:
|
| |
+ namespace: A string of repo namespace
|
| |
+ repo: A string of repo name
|
| |
+ branches: A list of str of branches requester would like to unretire.
|
| |
+ """
|
| |
+ global_component = repo
|
| |
+ component_type = self._namespace_to_pdc(namespace)
|
| |
+ branches_with_eol = self._get_proper_eol_for_branches(branches)
|
| |
+
|
| |
+ for key, value in branches_with_eol.items():
|
| |
+ branch = key
|
| |
+ eol = value
|
| |
+ pdc.adjust_eol(global_component=global_component, component_type=component_type,
|
| |
+ branch=branch, eol=eol)
|
| |
+
|
| |
+ @staticmethod
|
| |
+ def _namespace_to_pdc(namespace):
|
| |
+ """Internal method to translate a dist-git namespace to a PDC
|
| |
+ component type."""
|
| |
+ namespace_to_pdc = {
|
| |
+ "rpms": "rpm",
|
| |
+ "modules": "module",
|
| |
+ "container": "container",
|
| |
+ }
|
| |
+ if namespace not in namespace_to_pdc:
|
| |
+ raise ValueError('The namespace "{0}" is not supported'.format(namespace))
|
| |
+ else:
|
| |
+ return namespace_to_pdc[namespace]
|
| |
+
|
| |
+ def _get_proper_eol_for_branches(self, tags_to_unblock: list) -> dict:
|
| |
+ """
|
| |
+ Get proper eol date for each branch and create the dict with it.
|
| |
+
|
| |
+ :arg tags_to_unblock: Tags that requester would like to unblock.
|
| |
+
|
| |
+ :returns: Dictionary with key as branch name and value as eol.
|
| |
+ """
|
| |
+ last_branch = self._get_last_fedora_version_branch_name()
|
| |
+
|
| |
+ bodhi_endpoint = "https://bodhi.fedoraproject.org/releases/"
|
| |
+ response = requests.get(url=bodhi_endpoint)
|
| |
+ count_of_version_on_bodhi = response.json()["total"]
|
| |
+
|
| |
+ params = {"rows_per_page": count_of_version_on_bodhi}
|
| |
+ response = requests.get(url=bodhi_endpoint, params=params)
|
| |
+
|
| |
+ releases = response.json()["releases"]
|
| |
+ branches = {}
|
| |
+
|
| |
+ for release in releases:
|
| |
+ tag = release["dist_tag"]
|
| |
+ eol = release["eol"]
|
| |
+ if tag in tags_to_unblock:
|
| |
+ if tag == last_branch:
|
| |
+ branches["rawhide"] = "2222-01-01"
|
| |
+ else:
|
| |
+ branches[tag] = eol
|
| |
+
|
| |
+ return branches
|
| |
+
|
| |
+ @staticmethod
|
| |
+ def _is_url_exist(url: str) -> bool:
|
| |
+ """
|
| |
+ Check whether url exist.
|
| |
+
|
| |
+ :arg url: Url that might exist
|
| |
+
|
| |
+ :returns: True if url exist, otherwise False
|
| |
+ """
|
| |
+ response = requests.get(url=url)
|
| |
+ return response.status_code == 200
|
| |
+
|
| |
+
|
| |
+ def _get_arguments(args):
|
| |
+ """Load and parse the CLI arguments.
|
| |
+
|
| |
+ :arg args: Script arguments
|
| |
+
|
| |
+ :returns: Parsed arguments
|
| |
+ """
|
| |
+ parser = argparse.ArgumentParser(
|
| |
+ description="Processor for PDC 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:
|
| |
+ """
|
| |
+ Setup 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 = toml.load(args.config)
|
| |
+ parsed_config = config.get("consumer_config", {}).get("default", {})
|
| |
+ parsed_config.update(
|
| |
+ config.get("consumer_config", {}).get("pdc_unretire_packages", {})
|
| |
+ )
|
| |
+
|
| |
+ ticket = args.ticket
|
| |
+
|
| |
+ pagure_io = pagure.set_pagure(parsed_config)
|
| |
+ issue = pagure_io.get_issue(ticket, PROJECT_NAMESPACE)
|
| |
+
|
| |
+ # Convert issue to message
|
| |
+ message = IssueNewV1()
|
| |
+ message.body["issue"] = issue
|
| |
+ message.body["project"] = {"fullname": PROJECT_NAMESPACE}
|
| |
+ _log.debug("Message prepared: {}".format(message.body))
|
| |
+
|
| |
+ PDCUnretirePackages().process(
|
| |
+ config=parsed_config,
|
| |
+ message=message,
|
| |
+ )
|
| |
+
|
| |
+
|
| |
+ if __name__ == "__main__":
|
| |
+ try:
|
| |
+ main(sys.argv[1:])
|
| |
+ except KeyboardInterrupt:
|
| |
+ pass
|
| |
I added new pdc_unretire_packages plugin.
Methods that are working with git moved to git util.
Methods that are working with pagure moved to pagure util.
Tests for new code in utils.
Optimization pdc_unretire_packages code as a reaction to comments from the previous PR.