--- /dev/null
+---
+name: Create backport trackers for trackers in "Pending Backport" state
+on:
+ # To manually trigger this: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch
+ workflow_dispatch:
+ inputs:
+ issues:
+ description: 'whitespace-separated list of issue numbers'
+ type: string
+ default: ''
+ debug:
+ description: '--debug: Show debug-level messages'
+ default: false
+ type: boolean
+ resolveParent:
+ description: '--resolve-parent: Resolve parent issue if all backports resolved/rejected'
+ default: false
+ type: boolean
+ force:
+ description: >
+ --force: When issue numbers provided, process them even if not in
+ 'Pending Backport' status.
+ Otherwise, process all issues in 'Pending Backport' status even if
+ already processed (tag 'backport_processed' added)'
+ default: false
+ type: boolean
+ dryRun:
+ description: '--dry-run: Do not write anything to Redmine'
+ default: false
+ type: boolean
+ schedule:
+ # Every 5 minutes: https://crontab.guru/every-5-minutes
+ - cron: '*/5 * * * *'
+jobs:
+ create-backports:
+ runs-on: ubuntu-latest
+ if: github.ref == 'refs/heads/main'
+ steps:
+ - uses: Bhacaz/checkout-files@e3e34e7daef91a5f237485bb88a260aee4be29dd
+ with:
+ files: src/script/backport-create-issue src/script/requirements.backport-create-issue.txt
+ - uses: actions/setup-python@v4
+ with:
+ python-version: '>=3.6'
+ cache: 'pip'
+ cache-dependency-path: src/script/requirements.backport-create-issue.txt
+ - run: pip install -r src/script/requirements.backport-create-issue.txt
+ - run: python3 src/script/backport-create-issue ${{ inputs.debug && '--debug' || '' }} ${{ inputs.resolveParent && '--resolve-parent' || '' }} ${{ inputs.force && '--force' || '' }} ${{ inputs.dryRun && '--dry-run' || '' }} ${{ inputs.issues }}
+ env:
+ REDMINE_API_KEY: ${{ secrets.REDMINE_API_KEY_BACKPORT_BOT }}
redmine_endpoint = "https://tracker.ceph.com"
project_name = "Ceph"
release_id = 16
+custom_field_tag = 'cf_3'
+tag_separator = ' '
+tag_backport_processed = 'backport_processed'
delay_seconds = 5
redmine_key_file="~/.redmine_key"
+redmine_key_env="REDMINE_API_KEY"
#
# NOTE: release_id is hard-coded because
# http://www.redmine.org/projects/redmine/wiki/Rest_CustomFields
def usage():
logging.error("Redmine credentials are required to perform this operation. "
- "Please provide either a Redmine key (via %s) "
+ "Please provide either a Redmine key (via %s or $%s) "
"or a Redmine username and password (via --user and --password). "
"Optionally, one or more issue numbers can be given via positional "
"argument(s). In the absence of positional arguments, the script "
- "will loop through all issues in Pending Backport status." % redmine_key_file)
+ "will loop through all issues in Pending Backport status.",
+ redmine_key_file, redmine_key_env)
exit(-1)
def parse_arguments():
action="store_true")
parser.add_argument("--dry-run", help="Do not write anything to Redmine",
action="store_true")
- parser.add_argument("--force", help="Create backport issues even if status not Pending Backport",
+ parser.add_argument("--force", help="When issue numbers provided, process "
+ "them even if not in 'Pending Backport' status. "
+ "Otherwise, process all issues in 'Pending Backport' "
+ "status even if already processed "
+ f"(tag '{tag_backport_processed}' added)",
action="store_true")
return parser.parse_args()
+
def set_logging_level(a):
if a.debug:
logging.basicConfig(level=logging.DEBUG)
elif redmine_key:
logging.info("Redmine key was read from '%s'; using it" % redmine_key_file)
return Redmine(redmine_endpoint, key=redmine_key)
+ elif os.getenv(redmine_key_env):
+ logging.info("Redmine key was read from '$%s'; using it", redmine_key_env)
+ return Redmine(redmine_endpoint, key=os.getenv(redmine_key_env))
else:
usage()
else:
logging.debug("Some backport issues are still unresolved: leaving parent issue open")
+
+def mark_as_processed(r, issue):
+ """
+ This script will add a custom Tag to indicate whether the tracker was
+ already processed for backport tracker creation.
+ """
+ custom_fields = list(issue['custom_fields'].values())
+ for i, field in enumerate(custom_fields):
+ if field['name'] == 'Tags':
+ if tag_backport_processed not in field['value']:
+ if field['value']:
+ custom_fields[i]['value'] += (tag_separator +
+ tag_backport_processed)
+ else:
+ custom_fields[i]['value'] = tag_backport_processed
+ logging.info("%s adding tag '%s'", url(issue),
+ tag_backport_processed)
+ r.issue.update(issue.id, custom_fields=custom_fields)
+ return
+
+
def iterate_over_backports(r, issues, dry_run=False):
counter = 0
for issue in issues:
if len(issue['backports']) == 0:
logging.error(url(issue) + " the backport field is empty")
update_relations(r, issue, dry_run)
+ if not dry_run:
+ mark_as_processed(r, issue)
print(' \r', end='', flush=True)
logging.info("Processed {} issues".format(counter))
return None
issue_id=issue_list,
status_id=pending_backport_status_id)
else:
- if args.force:
- logging.warn("ignoring --force option, which can only be used with an explicit issue list")
- issues = redmine.issue.filter(project_id=ceph_project_id,
- status_id=pending_backport_status_id)
+ if args.force or args.resolve_parent:
+ if args.force:
+ logging.warn("--force option was given: ignoring '%s' tag!",
+ tag_backport_processed)
+ issues = redmine.issue.filter(project_id=ceph_project_id,
+ status_id=pending_backport_status_id)
+ else:
+ # https://python-redmine.com/resources/issue.html#filter
+ issues = redmine.issue.filter(project_id=ceph_project_id,
+ status_id=pending_backport_status_id,
+ **{
+ custom_field_tag:
+ '!~' +
+ tag_backport_processed})
if force_create:
logging.info("Processing {} issues regardless of status"
.format(len(issues)))