From 99a1503b1e0823d3ac7494ff171f48f09200f719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 26 Jun 2025 15:12:15 +0200 Subject: [PATCH 1/2] Add a script to help with jira requirements for backports --- jira-backport/README.md | 33 ++++++++ jira-backport/jira-backport.py | 136 +++++++++++++++++++++++++++++++++ jira-backport/requirements.txt | 2 + 3 files changed, 171 insertions(+) create mode 100644 jira-backport/README.md create mode 100644 jira-backport/jira-backport.py create mode 100644 jira-backport/requirements.txt diff --git a/jira-backport/README.md b/jira-backport/README.md new file mode 100644 index 0000000..d731124 --- /dev/null +++ b/jira-backport/README.md @@ -0,0 +1,33 @@ +# Create jira bugs for backports from upstream + +Create bugs for merging backports: +- run script with the URL of the PR +- check if there is an open bug for the component and release +- create new bug with + - component "Cloud Compute / OpenStack Provider" (for CAPO and CPO - we don't sync NFS to stable branches) + - type bug + - prio normal + - Affects version: target branch + - labels: Triaged + - description +- create a dependent bug if none already + - search for open bugs with upper version +- tag PR with the new bug + +## Usage + +Install dependencies: +``` +❯ pip install -r requirements.txt +``` + +Export both the `JIRA_TOKEN` and `GITHUB_TOKEN` environment variables with valid personal access tokens for [Jira](https://issues.redhat.com/secure/ViewProfile.jspa?selectedTab=com.atlassian.pats.pats-plugin:jira-user-personal-access-tokens) and [Github](https://github.com/settings/tokens). + +Then run the script and pass it the URL of a pull request: + +``` +❯ python ./jira-backport.py https://github.com/openshift/cluster-api-provider-openstack/pull/346 +no existing issue... will create one +created issue: OCPBUGS-58028 +Retitling PR to: OCPBUGS-58028: Merge https://github.com/kubernetes-sigs/cluster-api-provider-openstack:release-0.10 into release-4.16 +``` diff --git a/jira-backport/jira-backport.py b/jira-backport/jira-backport.py new file mode 100644 index 0000000..8ab52ee --- /dev/null +++ b/jira-backport/jira-backport.py @@ -0,0 +1,136 @@ +from jira import JIRA +from github import Github +from github import Auth +import os +import re +import sys + +# TODO +# - Set Target version to be the same as the affected version +# - Set Release notes not required +# - Set Assignee +# - Create parent bug if needed +# - Work on main/master and not just "release-xxx" branches + + +def getUpstreamBranch(repo, branch): + y_version = branch.split("release-4.")[1] + # OCP follows kube release cycle, but with 13 y-versions difference + kube_y_version = int(y_version) + 13 + if repo.name == "cluster-api-provider-openstack": + if int(y_version) == 15: + return "release-0.8" + elif int(y_version) <= 17: + return "release-0.10" + elif int(y_version) <= 18: + return "release-0.11" + else: + return "release-0.12" + else: + return "release-1.{}".format(kube_y_version) + + +def getPR(url): + m = re.match(r'.*github.com\/(?P[-_\w]+\/[-_\w]+)\/pull\/(?P\d+)', url) + repo_name = m.group('repo_name') + pr_number = int(m.group('pr_number')) + + repo=github.get_repo(repo_name) + pr = repo.get_pull(pr_number) + + return pr + +def findOrCreateJira(repo, branch): + issue = findJira(repo, branch) + + if issue: + print("found existing issue: {}".format(issue)) + else: + print("no existing issue... will create one") + issue = createJira(repo, branch) + print("created issue: {}".format(issue)) + return issue + + +def findJira(repo, branch): + version = branch.split("release-")[1] + + if repo.name in ["cloud-provider-openstack", "cluster-api-provider-openstack"]: + component = "Cloud Compute / OpenStack Provider" + else: + return + query = 'summary ~ "Sync stable branch for {project}" and project = "OpenShift Bugs" and component = "{component}" and affectedVersion = {version}.z and status not in (Verified, Closed)'.format( + project = repo.name, + component = component, + version = version, + ) + issues = jira.search_issues(query, maxResults=1) + for issue in issues: + return issue + +def createJira(repo, branch): + upstream_branch = getUpstreamBranch(repo, branch) + summary = 'Sync stable branch for {repo} {upstream_branch} into {branch}'.format( + branch=branch, + upstream_branch=upstream_branch, + repo=repo.name, + ) + description = """Description of problem:{{code:none}} +{branch} of {repo} is missing some commits that were backported in upstream project into the {upstream_branch} branch. +We should import them in our downstream fork. +{{code}}""".format(branch=branch, + upstream_branch=upstream_branch, + repo=repo.full_name) + version = branch.split("release-")[1] + + # TODO(mandre) add test coverage and target version so that the but does + # not remove the triage label + # also assignee + issue_dict = { + 'project': {'key': 'OCPBUGS'}, + 'summary': summary, + 'description': description, + 'issuetype': {'name': 'Bug'}, + 'priority': {'name': 'Normal'}, + 'components': [{'name': 'Cloud Compute / OpenStack Provider'}], + 'labels': ['Triaged'], + 'versions': [{'name': "{}.z".format(version)}], + } + return jira.create_issue(fields=issue_dict) + +def retitlePR(pr, issue_key): + m = re.match(r'(OCPBUGS-\d+:\s*)?(?P.+)', pr.title) + new_title = "{}: {}".format(issue_key, m.group('title')) + if pr.title != new_title: + print("Retitling PR to: {}".format(new_title)) + pr.create_issue_comment("/retitle {}".format(new_title)) + + +if len(sys.argv) < 1: + print("Pass the URL of a github PR from mergebot") + exit() + +# Get yours at +# https://issues.redhat.com/secure/ViewProfile.jspa?selectedTab=com.atlassian.pats.pats-plugin:jira-user-personal-access-tokens +jira_token = os.environ.get('JIRA_TOKEN', "") +if jira_token == "": + print("Missing or empty JIRA_TOKEN environment variable") + exit() + +# Get yours at https://github.com/settings/tokens +gh_token = os.environ.get('GITHUB_TOKEN', "") +if gh_token == "": + print("Missing or empty GITHUB_TOKEN environment variable") + exit() + +jira = JIRA(server="https://issues.redhat.com", token_auth=jira_token) +github = Github(auth=Auth.Token(gh_token)) + +url = sys.argv[1] +pr = getPR(url) + +issue = findOrCreateJira(pr.base.repo, pr.base.ref) + +# Retitle github PR if needed +if issue: + retitlePR(pr, issue.key) diff --git a/jira-backport/requirements.txt b/jira-backport/requirements.txt new file mode 100644 index 0000000..ad80b7a --- /dev/null +++ b/jira-backport/requirements.txt @@ -0,0 +1,2 @@ +jira +PyGithub From d4b520868b36cc5d17f9d6e6d712df8af414ff25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= <m.andre@redhat.com> Date: Thu, 26 Jun 2025 15:27:33 +0200 Subject: [PATCH 2/2] Add markers for dependencies To make it easier to run with `uv`. --- jira-backport/jira-backport.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/jira-backport/jira-backport.py b/jira-backport/jira-backport.py index 8ab52ee..29d3fb9 100644 --- a/jira-backport/jira-backport.py +++ b/jira-backport/jira-backport.py @@ -1,3 +1,10 @@ +# /// script +# dependencies = [ +# "jira", +# "PyGithub", +# ] +# /// + from jira import JIRA from github import Github from github import Auth