Skip to content

Commit f03aa38

Browse files
committed
Merge PR ceph#64417 into main
* refs/pull/64417/head: script/redmine-upkeep: trigger on merged PRs .github: fix workflow format error Reviewed-by: Ernesto Puerta <[email protected]>
2 parents 460efbc + 14cd29f commit f03aa38

File tree

2 files changed

+162
-10
lines changed

2 files changed

+162
-10
lines changed

.github/workflows/redmine-upkeep.yml

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ on:
1919
type: boolean
2020
schedule:
2121
- cron: '*/30 * * * *'
22+
pull_request:
23+
types: [closed]
24+
branches:
25+
- main
2226
# TODO enable/setup after upkeep has caught up
2327
# push:
2428
# tags:
@@ -51,11 +55,42 @@ jobs:
5155
- name: install dependencies
5256
run: pip install -r ceph/src/script/requirements.redmine-upkeep.txt
5357

54-
- run: >
58+
- name: Run redmine-upkeep via workflow_dispatch
59+
if: github.event_name == 'workflow_dispatch'
60+
run: >
61+
python3 ceph/src/script/redmine-upkeep.py
62+
--github-action
63+
--git-dir=./ceph/
64+
(inputs.debug && '--debug' || '')
65+
format('--limit={0}', inputs.limit)
66+
env:
67+
REDMINE_API_KEY: ${{ secrets.REDMINE_API_KEY_BACKPORT_BOT }}
68+
69+
- name: Run redmine-upkeep via schedule
70+
if: github.event_name == 'schedule'
71+
run: >
72+
python3 ceph/src/script/redmine-upkeep.py
73+
--github-action
74+
--git-dir=./ceph/
75+
env:
76+
REDMINE_API_KEY: ${{ secrets.REDMINE_API_KEY_BACKPORT_BOT }}
77+
78+
- name: Run redmine-upkeep via test push
79+
if: github.event_name == 'push' && github.ref == 'refs/heads/feature/redmine-upkeep'
80+
run: >
81+
python3 ceph/src/script/redmine-upkeep.py
82+
--github-action
83+
--git-dir=./ceph/
84+
env:
85+
REDMINE_API_KEY: ${{ secrets.REDMINE_API_KEY_BACKPORT_BOT }}
86+
87+
- name: Run redmine-upkeep via merge
88+
if: github.event.pull_request.merged == true
89+
run: >
5590
python3 ceph/src/script/redmine-upkeep.py
5691
--github-action
5792
--git-dir=./ceph/
58-
${{ github.event_name == 'workflow_dispatch' && (inputs.debug && '--debug' || '') || '' }}
59-
${{ github.event_name == 'workflow_dispatch' && format('--limit={}', inputs.limit) || '' }}
93+
--pull-request=${{ github.event.pull_request.number }}
94+
--merge-commit=${{ github.event.pull_request.merge_commit_sha }}
6095
env:
6196
REDMINE_API_KEY: ${{ secrets.REDMINE_API_KEY_BACKPORT_BOT }}

src/script/redmine-upkeep.py

Lines changed: 124 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import re
2020
import signal
2121
import sys
22+
import textwrap
2223

2324
from datetime import datetime, timedelta, timezone
2425
from getpass import getuser
@@ -101,6 +102,31 @@ def process(self, msg, kwargs):
101102
def gitauth():
102103
return (GITHUB_USER, GITHUB_TOKEN)
103104

105+
def post_github_comment(session, pr_id, body):
106+
"""Helper to post a comment to a GitHub PR."""
107+
if RedmineUpkeep.GITHUB_RATE_LIMITED:
108+
log.warning("GitHub API rate limit hit previously. Skipping posting comment.")
109+
return False
110+
111+
log.info(f"Posting a comment to GitHub PR #{pr_id}.")
112+
endpoint = f"{GITHUB_API_ENDPOINT}/issues/{pr_id}/comments"
113+
payload = {'body': body}
114+
try:
115+
response = session.post(endpoint, auth=gitauth(), json=payload)
116+
response.raise_for_status()
117+
log.info(f"Successfully posted comment to PR #{pr_id}.")
118+
return True
119+
except requests.exceptions.HTTPError as e:
120+
if e.response.status_code == 403 and "rate limit exceeded" in e.response.text:
121+
log.error(f"GitHub API rate limit exceeded when commenting on PR #{pr_id}.")
122+
RedmineUpkeep.GITHUB_RATE_LIMITED = True
123+
else:
124+
log.error(f"GitHub API error posting comment to PR #{pr_id}: {e} - Response: {e.response.text}")
125+
return False
126+
except requests.exceptions.RequestException as e:
127+
log.error(f"Network or request error posting comment to GitHub PR #{pr_id}: {e}")
128+
return False
129+
104130
class IssueUpdate:
105131
def __init__(self, issue, github_session, git_repo):
106132
self.issue = issue
@@ -266,8 +292,10 @@ def __init__(self, args):
266292
self.R = self._redmine_connect()
267293
self.limit = args.limit
268294
self.session = requests.Session()
269-
self.issue_id = args.issue # Store issue_id from args
270-
self.revision_range = args.revision_range # Store revision_range from args
295+
self.issue_id = args.issue
296+
self.revision_range = args.revision_range
297+
self.pull_request_id = args.pull_request
298+
self.merge_commit = args.merge_commit
271299

272300
self.issues_inspected = 0
273301
self.issues_modified = 0
@@ -699,10 +727,90 @@ def filter_and_process_issues(self):
699727
elif self.revision_range is not None:
700728
log.info(f"Processing in revision-range mode for range: {self.revision_range}.")
701729
self._execute_revision_range()
730+
elif self.pull_request_id is not None:
731+
log.info(f"Processing in pull-request mode for PR #{self.pull_request_id}.")
732+
self._execute_pull_request()
702733
else:
703734
log.info(f"Processing in filter-based mode with a limit of {self.limit} issues.")
704735
self._execute_filters()
705736

737+
def _execute_pull_request(self):
738+
"""
739+
Handles the --pull-request logic.
740+
1. Finds Redmine issues linked to the PR and runs transforms on them.
741+
2. If none, inspects the local merge commit for "Fixes:" tags.
742+
3. If tags are found, comments on the GH PR to ask the author to link the ticket.
743+
"""
744+
pr_id = self.pull_request_id
745+
merge_commit_sha = self.merge_commit
746+
log.info(f"Querying Redmine for issues linked to PR #{pr_id} and merge commit {merge_commit_sha}")
747+
748+
filters = {
749+
"project_id": self.project_id,
750+
"status_id": "*",
751+
f"cf_{REDMINE_CUSTOM_FIELD_ID_PULL_REQUEST_ID}": pr_id,
752+
}
753+
issues = self.R.issue.filter(**filters)
754+
755+
processed_issue_ids = set()
756+
if len(issues) > 0:
757+
log.info(f"Found {len(issues)} linked issue(s). Applying transformations.")
758+
for issue in issues:
759+
self._process_issue_transformations(issue)
760+
processed_issue_ids.add(issue.id)
761+
# Still, check commit logs.
762+
else:
763+
log.warning(f"No Redmine issues found linked to PR #{pr_id}. Inspecting local merge commit {merge_commit_sha} for 'Fixes:' tags.")
764+
765+
found_tracker_ids = set()
766+
try:
767+
revrange = f"{merge_commit_sha}^..{merge_commit_sha}"
768+
log.info(f"Iterating commits {revrange}")
769+
for commit in self.G.iter_commits(revrange):
770+
log.info(f"Inspecting commit {commit.hexsha}")
771+
772+
fixes_regex = re.compile(r"Fixes: https://tracker.ceph.com/issues/(\d+)", re.MULTILINE)
773+
commit_fixes = set(fixes_regex.findall(commit.message))
774+
for tracker_id in commit_fixes:
775+
log.info(f"Commit {commit.hexsha} claims to fix https://tracker.ceph.com/issues/{tracker_id}")
776+
found_tracker_ids.add(int(tracker_id))
777+
except git.exc.GitCommandError as e:
778+
log.error(f"Git command failed for commit SHA '{merge_commit_sha}': {e}. Ensure the commit exists in the local repository.")
779+
return
780+
781+
# Are the found_tracker_ids (including empty set) a proper subset of processed_issue_ids?
782+
log.debug(f"found_tracker_ids = {found_tracker_ids}")
783+
log.debug(f"processed_issue_ids = {processed_issue_ids}")
784+
if found_tracker_ids <= processed_issue_ids:
785+
log.info("All commits reference trackers already processed or no tracker referenced to be fixed.")
786+
return
787+
788+
log.info(f"Found 'Fixes:' tags for tracker(s) #{', '.join([str(x) for x in found_tracker_ids])} in commits.")
789+
790+
tracker_links = "\n".join([f"https://tracker.ceph.com/issues/{tid}" for tid in found_tracker_ids])
791+
comment_body = f"""
792+
793+
This is an automated message by src/script/redmine-upkeep.py.
794+
795+
I found one or more 'Fixes:' tags in the commit messages in
796+
797+
`git log {revrange}`
798+
799+
The referenced tickets are:
800+
801+
{tracker_links}
802+
803+
Those tickets do not reference this merged Pull Request. If this
804+
Pull Request merge resolves any of those tickets, please update the
805+
"Pull Request ID" field on each ticket. A future run of this
806+
script will appropriately update them.
807+
808+
"""
809+
comment_body = textwrap.dedent(comment_body)
810+
log.debug(f"Leaving comment:\n{comment_body}")
811+
812+
post_github_comment(self.session, pr_id, comment_body)
813+
706814
def _execute_revision_range(self):
707815
log.info(f"Processing issues based on revision range: {self.revision_range}")
708816
try:
@@ -737,9 +845,6 @@ def _execute_revision_range(self):
737845
except redminelib.exceptions.ResourceAttrError as e:
738846
log.error(f"Redmine API error for merge commit {commit}: {e}")
739847
raise
740-
except Exception as e:
741-
log.exception(f"Error processing issues for merge commit {commit}: {e}")
742-
raise
743848
except git.exc.GitCommandError as e:
744849
log.error(f"Git command error for revision range '{self.revision_range}': {e}")
745850
raise
@@ -792,12 +897,24 @@ def main():
792897
parser.add_argument('--limit', dest='limit', action='store', type=int, default=200, help='limit processed issues')
793898
parser.add_argument('--git-dir', dest='git', action='store', default=".", help='git directory')
794899

900+
# Mutually exclusive group for different modes of operation
795901
group = parser.add_mutually_exclusive_group()
796-
group.add_argument('--issue', dest='issue', action='store', help='issue to check')
902+
group.add_argument('--issue', dest='issue', action='store', help='Single issue ID to check.')
797903
group.add_argument('--revision-range', dest='revision_range', action='store',
798-
help='Git revision range (e.g., "v12.2.2..v12.2.3") to find merge commits and process related issues.')
904+
help='Git revision range to find merge commits and process related issues.')
905+
group.add_argument('--pull-request', dest='pull_request', type=int, action='store',
906+
help='Pull Request ID to lookup (requires --merge-commit).')
907+
908+
parser.add_argument('--merge-commit', dest='merge_commit', action='store',
909+
help='Merge commit SHA for the PR (requires --pull-request).')
799910

800911
args = parser.parse_args(sys.argv[1:])
912+
913+
# Ensure --pull-request and --merge-commit are used together
914+
if args.pull_request and not args.merge_commit:
915+
parser.error("--pull-request and --merge-commit must be used together.")
916+
sys.exit(1)
917+
801918
log.info("Redmine Upkeep Script starting.")
802919

803920
global IS_GITHUB_ACTION

0 commit comments

Comments
 (0)