From: Ernesto Puerta <37327689+epuertat@users.noreply.github.com> Date: Fri, 5 Aug 2022 08:56:36 +0000 (+0200) Subject: .github/workflows: add create-backport action X-Git-Tag: v18.0.0~301^2 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=a92bf26f056cac2d76e9b5fed9ac0d219170837f;p=ceph-ci.git .github/workflows: add create-backport action Currently there's a cron job in a teuthology VM running a script to find all trackers in needs-backports and to create their corresponding backport trackers. This is done through the [Backport Bot](https://tracker.ceph.com/users/12172) Redmine account. This PR intends to run this cron job task as a periodic Github Action. Signed-off-by: Ernesto Puerta <37327689+epuertat@users.noreply.github.com> Signed-off-by: Ernesto Puerta --- diff --git a/.github/workflows/create-backport-trackers.yml b/.github/workflows/create-backport-trackers.yml new file mode 100644 index 00000000000..b3525d9e94e --- /dev/null +++ b/.github/workflows/create-backport-trackers.yml @@ -0,0 +1,50 @@ +--- +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 }} diff --git a/src/script/backport-create-issue b/src/script/backport-create-issue index 8336ac29348..e2e2298f977 100755 --- a/src/script/backport-create-issue +++ b/src/script/backport-create-issue @@ -39,8 +39,12 @@ from redminelib.exceptions import ResourceAttrError 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 @@ -60,11 +64,12 @@ resolve_parent = None 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(): @@ -78,10 +83,15 @@ 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) @@ -117,6 +127,9 @@ def connect_to_redmine(a): 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() @@ -285,6 +298,27 @@ def maybe_resolve(issue, backports, dry_run): 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: @@ -303,6 +337,8 @@ def iterate_over_backports(r, issues, dry_run=False): 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 @@ -337,10 +373,20 @@ if __name__ == '__main__': 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))) diff --git a/src/script/requirements.backport-create-issue.txt b/src/script/requirements.backport-create-issue.txt new file mode 100644 index 00000000000..832772fd961 --- /dev/null +++ b/src/script/requirements.backport-create-issue.txt @@ -0,0 +1 @@ +python-redmine == 2.3.0