diff --git a/amqp-listen-gitea.py b/amqp-listen-gitea.py new file mode 100644 index 00000000..2d82ac22 --- /dev/null +++ b/amqp-listen-gitea.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python3 +import pika +import sys +import os +import argparse +import json +import subprocess +import requests +import re +import logging +import datetime +from urllib.parse import urlencode, urlunparse, urlparse + +USER_AGENT = 'amqp-listen-gitea.py (https://github.com/os-autoinst/scripts)' +dry_run=False + +logging.basicConfig() +log = logging.getLogger(sys.argv[0] if __name__ == "__main__" else __name__) + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--url", help="AMQP URL", default="amqps://opensuse:opensuse@rabbit.opensuse.org") + parser.add_argument("--prefix", help="Event prefix to collect", default="opensuse.src.") + parser.add_argument("--event-type", help="Event type to collect", default="pull_request_review_request.review_requested") + parser.add_argument("--myself", help="Username of bot", default="qam-openqa") + parser.add_argument("--openqa-host", help="OpenQA instance url", default="http://localhost:9526") + parser.add_argument("--verbose", help="Verbosity", default="1", type=int, choices=[0, 1, 2, 3]) + parser.add_argument("--simulate-review-requested-event", help="Behave as if a pull_request_review_request.review_requested was received") + parser.add_argument("--simulate-build-finished-event", help="Behave as if build is marked as finished") + parser.add_argument("--build-bot", help="Username of bot that approves when build is finished") + parser.add_argument("--branch", help="Target branch, eg. leap 16.0") + parser.add_argument("--project", help="Target project") + parser.add_argument("--store-amqp", help="Should the amqp event be stored", action='store_true', default=False) + args = parser.parse_args() + return args + + +def listen(args): + connection = pika.BlockingConnection(pika.URLParameters(args.url)) + channel = connection.channel() + channel.exchange_declare(exchange='pubsub', exchange_type='topic', passive=True, durable=True) + result = channel.queue_declare("", exclusive=True) + queue_name = result.method.queue + channel.queue_bind(exchange='pubsub', queue=queue_name,routing_key='#') + + def cb(ch, method, properties, body): + callback(ch, method, properties, body, args) + channel.basic_consume(queue_name, cb, auto_ack=True) + + print('[*] Waiting for logs. To exit press CTRL+C') + channel.start_consuming() + + +def callback(ch, method, properties, body, args): + # opensuse.src.someuser.pull_request_review_request.review_requested + if not method.routing_key.startswith(args.prefix): + if args.verbose >= 3: + print(" [ ] %r" % (method.routing_key)) + return + if args.event_type not in method.routing_key: + if args.verbose >= 2: + print(" [ ] %r" % (method.routing_key)) + return + if args.verbose >= 2: + print(" [x] %r" % (method.routing_key)) + data = json.loads(body) + + if args.store_amqp: + timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + filename = f"tests/data/gitea-amqp/amqp-{args.myself}-{args.event_type}-{timestamp}.json" + try: + with open(filename, 'w') as file_object: + json.dump(data, file_object,indent=4) + log.info(f"Storing review-requested file to {filename}") + print(f"{args.event_type} captured, saved to {filename}. Exiting") + exit() + + except IOError as e: + log.error(f"Error saving file: {e}") + + handle_review_request(data, args) + + +def simulate(args): + print('================= simulate') + # json_file = 'tests/data/gitea-amqp/minimal-payload-gitea-review-requested.json' + json_file = args.simulate_review_requested_event + with open(json_file, 'r') as f: + content = f.read() + data = json.loads(content) + handle_review_request(data, args) + +def simulate_build_finished_event(args): + print('================= simulate_build_finished_event') + json_file = args.simulate_build_finished_event + with open(json_file, 'r') as f: + content = f.read() + build_data = json.loads(content) + return handle_build_finished(build_data, args) + +def handle_review_request(data, args): + print("============== handle_review_request") + myself = args.myself + requested_reviewer = data['requested_reviewer']['username'] + if args.verbose >= 1: + print(" [x] Requested review from %r" % (requested_reviewer)) + if requested_reviewer != myself: + return + pull_request = data['pull_request'] + job_params = { + 'id': pull_request['id'], + 'label': pull_request['head']['label'], + 'branch': pull_request['head']['ref'], + 'sha': pull_request['head']['sha'], + 'pr_html_url': pull_request['html_url'], + 'clone_url': pull_request['head']['repo']['clone_url'], + 'repo_name': pull_request['head']['repo']['name'], + 'repo_api_url': data['repository']['url'], + 'repo_html_url': data['repository']['html_url'], + } + params = create_openqa_job_params(args, job_params) + job_url = openqa_schedule(args, params) + print(job_url) + gitea_post_status(job_params, job_url) + +def handle_build_finished(data, args): + print("============== handle_build_finished") + build_bot = args.build_bot + myself = args.myself + if build_bot == data["sender"]["username"]: + print(f"Build marked as finished by ({build_bot})") + else: + if args.verbose >= 1: + print(f"Aborting: PR approval is by {data["sender"]["username"]}, not by our bot {build_bot}") + return + + if (data["pull_request"]["base"]["label"] == args.branch and data["pull_request"]["base"]["repo"]["full_name"] == args.project): + + pull_request = data['pull_request'] + job_params = { + 'id': pull_request['id'], + 'label': pull_request['head']['label'], + 'branch': pull_request['head']['ref'], + 'sha': pull_request['head']['sha'], + 'pr_html_url': pull_request['html_url'], + 'clone_url': pull_request['head']['repo']['clone_url'], + 'repo_name': pull_request['head']['repo']['name'], # this should be full_name but openQA cli complains + 'repo_api_url': data['repository']['url'], + 'repo_html_url': data['repository']['html_url'], + } + packages_in_testing = get_packages_from_obs_project(job_params) + job_params["packages"] = packages_in_testing + params = create_openqa_job_params(args, job_params) + job_url = openqa_schedule(args, params) + print(job_url) + gitea_post_status(job_params, job_url) + + else: + if args.verbose >= 1: + print(f"Project and branch don't match {args.project}#{args.branch}") + return + +def get_packages_from_obs_project(job_params): + # This should be able to query the OBS project to get the list of packages + # per Pull request + pass + +def gitea_post_status(job_params, job_url): + print("============== gitea_post_status") + sha = job_params['sha'] + statuses_url = job_params['repo_api_url'] + '/statuses/' + job_params['sha']; + token = os.environ.get("GITEA_TOKEN") + headers = {'User-Agent': USER_AGENT, 'Accept': 'application/json', 'Authorization': 'token ' + token} + payload = { + 'context': 'qam-openqa', + 'description': "openQA check", + 'state': "pending", + 'target_url': job_url, + } + request_post(statuses_url, headers, payload) + + +def request_post(url, headers, payload): + print("============== request_post") + print(payload) + try: + content = requests.post(url, headers=headers, data=payload) + content.raise_for_status() + except requests.exceptions.RequestException as e: + log.error("Error while fetching %s: %s" % (url, str(e))) + raise (e) + + +def create_openqa_job_params(args, job_params): + print("============== create_openqa_job_params") + raw_url = job_params['repo_html_url'] + '/raw/branch/' + job_params['sha']; + statuses_url = job_params['repo_api_url'] + '/statuses/' + job_params['sha']; + params = { + 'BUILD': job_params['repo_name'] + '#' + job_params['sha'], + 'CASEDIR': job_params['clone_url'] + '#' + job_params['sha'], + '_GROUP_ID': '0', + 'PRIO': '100', + 'NEEDLES_DIR': '%%CASEDIR%%/needles', + + # set the URL for the scenario definitions YAML file so the Minion job will download it from GitHub + 'SCENARIO_DEFINITIONS_YAML_FILE': raw_url + '/' + 'scenario-definitions.yaml', + + # add "target URL" for the "Details" button of the CI status + 'CI_TARGET_URL': args.openqa_host, + + # set Gitea parameters so the Minion job will be able to report the status back to Gitea + 'GITEA_REPO': job_params['repo_name'], + 'GITEA_SHA': job_params['sha'], + 'GITEA_STATUSES_URL': statuses_url, + 'GITEA_PR_URL': job_params['pr_html_url'], + 'webhook_id': 'gitea:pr:' + str(job_params['id']), + } + return params + + +def openqa_cli(host, subcommand, cmds, dry_run=False): + print("============== openqa_cli") + client_args = [ + "openqa-cli", + subcommand, + "--host", + host, + ] + cmds + log.debug("openqa_cli: %s %s" % (subcommand, client_args)) + res = subprocess.run( + (["echo", "Simulating: "] if dry_run else []) + client_args, + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + if len(res.stderr): + log.warning(f"openqa_cli() {subcommand} stderr: {res.stderr}") + res.check_returncode() + return res.stdout.decode("utf-8"); + + +def openqa_schedule(args, params): + print("============== openqa_schedule") + scenario_url = 'https://raw.githubusercontent.com/os-autoinst/os-autoinst-distri-openQA/refs/heads/master/scenario-definitions.yaml' + scenario_yaml = fetch_url(scenario_url, request_type="text") + yaml_file = "/tmp/distri-openqa-scenario.yaml" + with open(yaml_file, 'w') as f: + f.write(scenario_yaml.decode("utf-8")) + cmd_args = [ + "--param-file", + "SCENARIO_DEFINITIONS_YAML=" + yaml_file, + "VERSION=Tumbleweed", + "DISTRI=openqa", + "FLAVOR=dev", + "ARCH=x86_64", + "HDD_1=opensuse-Tumbleweed-x86_64-20250920-minimalx@uefi.qcow2", + ] + for key in params: + cmd_args.append(key + '=' + params[key]) + output = openqa_cli(args.openqa_host, 'schedule', cmd_args, dry_run) + pattern = re.compile(r".*?(?Phttps?://\S+)", re.DOTALL) + + query_parameters = { + "build": params["BUILD"], + "distri":"openqa", + "version":"Tumbleweed" + } + + base_url = urlparse(args.openqa_host+"/tests/overview") + query_string = urlencode(query_parameters) + test_overview_url = urlunparse(base_url._replace(query=query_string)) + return test_overview_url + +def fetch_url(url, request_type="text"): + print("============== fetch_url") + try: + content = requests.get(url, headers={'User-Agent': USER_AGENT}) + content.raise_for_status() + except requests.exceptions.RequestException as e: + log.error("Error while fetching %s: %s" % (url, str(e))) + raise (e) + raw = content.content + if request_type == "json": + try: + content = content.json() + except json.decoder.JSONDecodeError as e: + log.error( + "Error while decoding JSON from %s -> >>%s<<: %s" + % (url, raw, str(e)) + ) + raise (e) + else: + content = raw + return content + + +if __name__ == "__main__": + args = parse_args() + if args.simulate_review_requested_event: + simulate(args) + elif(args.simulate_build_finished_event and args.build_bot): + simulate_build_finished_event(args) + else: + listen(args) diff --git a/tests/data/gitea-amqp/amqp-autogits_workflow_pr_bot-pull_request_review_approved-20251006-154156.json b/tests/data/gitea-amqp/amqp-autogits_workflow_pr_bot-pull_request_review_approved-20251006-154156.json new file mode 100644 index 00000000..c6b87be9 --- /dev/null +++ b/tests/data/gitea-amqp/amqp-autogits_workflow_pr_bot-pull_request_review_approved-20251006-154156.json @@ -0,0 +1,465 @@ +{ + "action": "reviewed", + "number": 160, + "pull_request": { + "id": 2932, + "url": "https://src.opensuse.org/products/PackageHub/pulls/160", + "number": 160, + "user": { + "id": 1652, + "login": "autogits_workflow_pr_bot", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "autogits_workflow_pr_bot@noreply.src.opensuse.org", + "avatar_url": "https://src.opensuse.org/avatar/dabd7e8102cdc7b31f1fa67c4bcb3908", + "html_url": "https://src.opensuse.org/autogits_workflow_pr_bot", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-08-13T08:32:42+02:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "autogits_workflow_pr_bot" + }, + "title": "Forwarded PRs: openQA", + "body": "This is a forwarded pull request by AutoGits PR Review Bot\nreferencing the following pull request(s):\n\nPR: pool/openQA!11\n\n### ManualMergeProject enabled. To merge, 'merge ok' is required by project maintainer in the project PR.", + "labels": [], + "milestone": null, + "assignee": null, + "assignees": [], + "requested_reviewers": [ + { + "id": 1778, + "login": "packagehub-review", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "packagehub-review@noreply.src.opensuse.org", + "avatar_url": "https://src.opensuse.org/avatar/484734e0814fd8726512df9f3b5b4373", + "html_url": "https://src.opensuse.org/packagehub-review", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-09-26T15:25:43+02:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "packagehub-review" + }, + { + "id": 1008, + "login": "autogits_obs_staging_bot", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "autogits_obs_staging_bot@noreply.src.opensuse.org", + "avatar_url": "https://src.opensuse.org/avatars/9aa9b21c0beaf80d4af7a0fca8a326862127f8b5fa68e47d0870800441ced967", + "html_url": "https://src.opensuse.org/autogits_obs_staging_bot", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2024-07-06T14:31:34+02:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "I stage proposed changes and see if they build.", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "autogits_obs_staging_bot" + } + ], + "requested_reviewers_teams": [], + "state": "open", + "draft": false, + "is_locked": false, + "comments": 3, + "review_comments": 1, + "additions": 1, + "deletions": 1, + "changed_files": 1, + "html_url": "https://src.opensuse.org/products/PackageHub/pulls/160", + "diff_url": "https://src.opensuse.org/products/PackageHub/pulls/160.diff", + "patch_url": "https://src.opensuse.org/products/PackageHub/pulls/160.patch", + "mergeable": true, + "merged": false, + "merged_at": null, + "merge_commit_sha": null, + "merged_by": null, + "allow_maintainer_edit": false, + "base": { + "label": "leap-16.0", + "ref": "leap-16.0", + "sha": "61505d870aa91436971e71508c07500633ed485dc1d1417ac968a85f1d9511c8", + "repo_id": 91295, + "repo": { + "id": 91295, + "owner": { + "id": 181, + "login": "products", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://src.opensuse.org/avatars/86024cad1e83101d97359d7351051156", + "html_url": "https://src.opensuse.org/products", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2023-09-20T11:20:19+02:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "products" + }, + "name": "PackageHub", + "full_name": "products/PackageHub", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "mirror": false, + "size": 9297, + "language": "", + "languages_url": "https://src.opensuse.org/api/v1/repos/products/PackageHub/languages", + "html_url": "https://src.opensuse.org/products/PackageHub", + "url": "https://src.opensuse.org/api/v1/repos/products/PackageHub", + "link": "", + "ssh_url": "gitea@src.opensuse.org:products/PackageHub.git", + "clone_url": "https://src.opensuse.org/products/PackageHub.git", + "original_url": "", + "website": "", + "stars_count": 1, + "forks_count": 8, + "watchers_count": 14, + "open_issues_count": 0, + "open_pr_counter": 13, + "release_counter": 0, + "default_branch": "leap-16.0", + "archived": false, + "created_at": "2024-09-17T14:31:57+02:00", + "updated_at": "2025-10-06T15:24:40+02:00", + "archived_at": "1970-01-01T01:00:00+01:00", + "permissions": { + "admin": false, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": false, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": false, + "has_pull_requests": true, + "has_projects": false, + "projects_mode": "all", + "has_releases": false, + "has_packages": false, + "has_actions": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "allow_manual_merge": true, + "autodetect_manual_merge": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha256", + "mirror_updated": "0001-01-01T00:00:00Z", + "topics": [], + "licenses": [] + } + }, + "head": { + "label": "PR_openQA#11", + "ref": "PR_openQA#11", + "sha": "71de5ba8c2289e20a07a20d1f836c37ee6437803435425a7cec507004457cc52", + "repo_id": 91295, + "repo": { + "id": 91295, + "owner": { + "id": 181, + "login": "products", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://src.opensuse.org/avatars/86024cad1e83101d97359d7351051156", + "html_url": "https://src.opensuse.org/products", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2023-09-20T11:20:19+02:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "products" + }, + "name": "PackageHub", + "full_name": "products/PackageHub", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "mirror": false, + "size": 9297, + "language": "", + "languages_url": "https://src.opensuse.org/api/v1/repos/products/PackageHub/languages", + "html_url": "https://src.opensuse.org/products/PackageHub", + "url": "https://src.opensuse.org/api/v1/repos/products/PackageHub", + "link": "", + "ssh_url": "gitea@src.opensuse.org:products/PackageHub.git", + "clone_url": "https://src.opensuse.org/products/PackageHub.git", + "original_url": "", + "website": "", + "stars_count": 1, + "forks_count": 8, + "watchers_count": 14, + "open_issues_count": 0, + "open_pr_counter": 13, + "release_counter": 0, + "default_branch": "leap-16.0", + "archived": false, + "created_at": "2024-09-17T14:31:57+02:00", + "updated_at": "2025-10-06T15:24:40+02:00", + "archived_at": "1970-01-01T01:00:00+01:00", + "permissions": { + "admin": false, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": false, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": false, + "has_pull_requests": true, + "has_projects": false, + "projects_mode": "all", + "has_releases": false, + "has_packages": false, + "has_actions": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "allow_manual_merge": true, + "autodetect_manual_merge": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha256", + "mirror_updated": "0001-01-01T00:00:00Z", + "topics": [], + "licenses": [] + } + }, + "merge_base": "61505d870aa91436971e71508c07500633ed485dc1d1417ac968a85f1d9511c8", + "due_date": null, + "created_at": "2025-10-06T15:24:38+02:00", + "updated_at": "2025-10-06T15:41:54+02:00", + "closed_at": null, + "pin_order": 0 + }, + "requested_reviewer": { + "id": 1008, + "login": "autogits_obs_staging_bot", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "autogits_obs_staging_bot@noreply.src.opensuse.org", + "avatar_url": "https://src.opensuse.org/avatars/9aa9b21c0beaf80d4af7a0fca8a326862127f8b5fa68e47d0870800441ced967", + "html_url": "https://src.opensuse.org/autogits_obs_staging_bot", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2024-07-06T14:31:34+02:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "I stage proposed changes and see if they build.", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "autogits_obs_staging_bot" + }, + "repository": { + "id": 91295, + "owner": { + "id": 181, + "login": "products", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://src.opensuse.org/avatars/86024cad1e83101d97359d7351051156", + "html_url": "https://src.opensuse.org/products", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2023-09-20T11:20:19+02:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "products" + }, + "name": "PackageHub", + "full_name": "products/PackageHub", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "mirror": false, + "size": 9297, + "language": "", + "languages_url": "https://src.opensuse.org/api/v1/repos/products/PackageHub/languages", + "html_url": "https://src.opensuse.org/products/PackageHub", + "url": "https://src.opensuse.org/api/v1/repos/products/PackageHub", + "link": "", + "ssh_url": "gitea@src.opensuse.org:products/PackageHub.git", + "clone_url": "https://src.opensuse.org/products/PackageHub.git", + "original_url": "", + "website": "", + "stars_count": 1, + "forks_count": 8, + "watchers_count": 14, + "open_issues_count": 0, + "open_pr_counter": 13, + "release_counter": 0, + "default_branch": "leap-16.0", + "archived": false, + "created_at": "2024-09-17T14:31:57+02:00", + "updated_at": "2025-10-06T15:24:40+02:00", + "archived_at": "1970-01-01T01:00:00+01:00", + "permissions": { + "admin": false, + "push": false, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": false, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": false, + "has_pull_requests": true, + "has_projects": false, + "projects_mode": "all", + "has_releases": false, + "has_packages": false, + "has_actions": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "allow_manual_merge": true, + "autodetect_manual_merge": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha256", + "mirror_updated": "0001-01-01T00:00:00Z", + "topics": [], + "licenses": [] + }, + "sender": { + "id": 1008, + "login": "autogits_obs_staging_bot", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "autogits_obs_staging_bot@noreply.src.opensuse.org", + "avatar_url": "https://src.opensuse.org/avatars/9aa9b21c0beaf80d4af7a0fca8a326862127f8b5fa68e47d0870800441ced967", + "html_url": "https://src.opensuse.org/autogits_obs_staging_bot", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2024-07-06T14:31:34+02:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "I stage proposed changes and see if they build.", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "autogits_obs_staging_bot" + }, + "commit_id": "", + "review": { + "type": "pull_request_review_approved", + "content": "Build successful" + } +} \ No newline at end of file diff --git a/tests/data/gitea-amqp/example-payload-gitea-review-requested.json b/tests/data/gitea-amqp/example-payload-gitea-review-requested.json new file mode 100644 index 00000000..ad00a18a --- /dev/null +++ b/tests/data/gitea-amqp/example-payload-gitea-review-requested.json @@ -0,0 +1,437 @@ +{ + "action": "review_requested", + "number": 2, + "pull_request": { + "id": 1934, + "url": "https://src.opensuse.org/tinita/myplayground/pulls/2", + "number": 2, + "user": { + "id": 1373, + "login": "tinita", + "login_name": "52fc4206003375ffbf106c244c56c7222bd6f77496bf9d2bc424293f0e4d4c81", + "source_id": 9, + "full_name": "Tina Müller", + "email": "tina.mueller@suse.com", + "avatar_url": "https://src.opensuse.org/avatars/a7cbbf47205685e65899495b73778f47a153fc03b4efbdf2b30e1b11ae742112", + "html_url": "https://src.opensuse.org/tinita", + "language": "en-US", + "is_admin": false, + "last_login": "2025-08-27T22:36:13+02:00", + "created": "2025-02-26T22:33:36+01:00", + "restricted": false, + "active": true, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 1, + "username": "tinita" + }, + "title": "WIP just a test, ignore", + "body": "", + "labels": [], + "milestone": null, + "assignee": null, + "assignees": [], + "requested_reviewers": [ + { + "id": 1196, + "login": "mkittler", + "login_name": "", + "source_id": 0, + "full_name": "Marius Kittler", + "email": "mkittler@noreply.src.opensuse.org", + "avatar_url": "https://src.opensuse.org/avatar/68978c76d4e4d5476873c61cf7e8f2a1", + "html_url": "https://src.opensuse.org/mkittler", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2024-10-18T14:19:24+02:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mkittler" + } + ], + "requested_reviewers_teams": [], + "state": "open", + "draft": false, + "is_locked": false, + "comments": 1, + "additions": 6, + "deletions": 0, + "changed_files": 1, + "html_url": "https://src.opensuse.org/tinita/myplayground/pulls/2", + "diff_url": "https://src.opensuse.org/tinita/myplayground/pulls/2.diff", + "patch_url": "https://src.opensuse.org/tinita/myplayground/pulls/2.patch", + "mergeable": true, + "merged": false, + "merged_at": null, + "merge_commit_sha": null, + "merged_by": null, + "allow_maintainer_edit": false, + "base": { + "label": "main", + "ref": "main", + "sha": "9bb6e3a0f89a3a3e48e50eb5120337384507b390059c344112016d654a17b54a", + "repo_id": 93650, + "repo": { + "id": 93650, + "owner": { + "id": 1373, + "login": "tinita", + "login_name": "", + "source_id": 0, + "full_name": "Tina Müller", + "email": "tinita@noreply.src.opensuse.org", + "avatar_url": "https://src.opensuse.org/avatars/a7cbbf47205685e65899495b73778f47a153fc03b4efbdf2b30e1b11ae742112", + "html_url": "https://src.opensuse.org/tinita", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-26T22:33:36+01:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 1, + "username": "tinita" + }, + "name": "myplayground", + "full_name": "tinita/myplayground", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "mirror": false, + "size": 37, + "language": "", + "languages_url": "https://src.opensuse.org/api/v1/repos/tinita/myplayground/languages", + "html_url": "https://src.opensuse.org/tinita/myplayground", + "url": "https://src.opensuse.org/api/v1/repos/tinita/myplayground", + "link": "", + "ssh_url": "gitea@src.opensuse.org:tinita/myplayground.git", + "clone_url": "https://src.opensuse.org/tinita/myplayground.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 0, + "watchers_count": 1, + "open_issues_count": 1, + "open_pr_counter": 1, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-03-05T14:05:15+01:00", + "updated_at": "2025-09-16T00:27:55+02:00", + "archived_at": "1970-01-01T01:00:00+01:00", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": false, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "has_pull_requests": true, + "has_projects": true, + "projects_mode": "all", + "has_releases": true, + "has_packages": true, + "has_actions": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "allow_manual_merge": false, + "autodetect_manual_merge": false, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha256", + "mirror_updated": "0001-01-01T00:00:00Z", + "topics": [], + "licenses": [] + } + }, + "head": { + "label": "testbranch", + "ref": "testbranch", + "sha": "ebe9d6a28e89632ce61cccacc196960a3543be17e4ec0ae86d54091f9a5574ae", + "repo_id": 93650, + "repo": { + "id": 93650, + "owner": { + "id": 1373, + "login": "tinita", + "login_name": "", + "source_id": 0, + "full_name": "Tina Müller", + "email": "tinita@noreply.src.opensuse.org", + "avatar_url": "https://src.opensuse.org/avatars/a7cbbf47205685e65899495b73778f47a153fc03b4efbdf2b30e1b11ae742112", + "html_url": "https://src.opensuse.org/tinita", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-26T22:33:36+01:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 1, + "username": "tinita" + }, + "name": "myplayground", + "full_name": "tinita/myplayground", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "mirror": false, + "size": 37, + "language": "", + "languages_url": "https://src.opensuse.org/api/v1/repos/tinita/myplayground/languages", + "html_url": "https://src.opensuse.org/tinita/myplayground", + "url": "https://src.opensuse.org/api/v1/repos/tinita/myplayground", + "link": "", + "ssh_url": "gitea@src.opensuse.org:tinita/myplayground.git", + "clone_url": "https://src.opensuse.org/tinita/myplayground.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 0, + "watchers_count": 1, + "open_issues_count": 1, + "open_pr_counter": 1, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-03-05T14:05:15+01:00", + "updated_at": "2025-09-16T00:27:55+02:00", + "archived_at": "1970-01-01T01:00:00+01:00", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": false, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "has_pull_requests": true, + "has_projects": true, + "projects_mode": "all", + "has_releases": true, + "has_packages": true, + "has_actions": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "allow_manual_merge": false, + "autodetect_manual_merge": false, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha256", + "mirror_updated": "0001-01-01T00:00:00Z", + "topics": [], + "licenses": [] + } + }, + "merge_base": "9bb6e3a0f89a3a3e48e50eb5120337384507b390059c344112016d654a17b54a", + "due_date": null, + "created_at": "2025-08-14T14:53:04+02:00", + "updated_at": "2025-09-23T16:59:20+02:00", + "closed_at": null, + "pin_order": 0 + }, + "requested_reviewer": { + "id": 1196, + "login": "mkittler", + "login_name": "", + "source_id": 0, + "full_name": "Marius Kittler", + "email": "mkittler@noreply.src.opensuse.org", + "avatar_url": "https://src.opensuse.org/avatar/68978c76d4e4d5476873c61cf7e8f2a1", + "html_url": "https://src.opensuse.org/mkittler", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2024-10-18T14:19:24+02:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mkittler" + }, + "repository": { + "id": 93650, + "owner": { + "id": 1373, + "login": "tinita", + "login_name": "", + "source_id": 0, + "full_name": "Tina Müller", + "email": "tinita@noreply.src.opensuse.org", + "avatar_url": "https://src.opensuse.org/avatars/a7cbbf47205685e65899495b73778f47a153fc03b4efbdf2b30e1b11ae742112", + "html_url": "https://src.opensuse.org/tinita", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-26T22:33:36+01:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 1, + "username": "tinita" + }, + "name": "myplayground", + "full_name": "tinita/myplayground", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "mirror": false, + "size": 37, + "language": "", + "languages_url": "https://src.opensuse.org/api/v1/repos/tinita/myplayground/languages", + "html_url": "https://src.opensuse.org/tinita/myplayground", + "url": "https://src.opensuse.org/api/v1/repos/tinita/myplayground", + "link": "", + "ssh_url": "gitea@src.opensuse.org:tinita/myplayground.git", + "clone_url": "https://src.opensuse.org/tinita/myplayground.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 0, + "watchers_count": 1, + "open_issues_count": 1, + "open_pr_counter": 1, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-03-05T14:05:15+01:00", + "updated_at": "2025-09-16T00:27:55+02:00", + "archived_at": "1970-01-01T01:00:00+01:00", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": false, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "has_pull_requests": true, + "has_projects": true, + "projects_mode": "all", + "has_releases": true, + "has_packages": true, + "has_actions": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "allow_manual_merge": false, + "autodetect_manual_merge": false, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha256", + "mirror_updated": "0001-01-01T00:00:00Z", + "topics": [], + "licenses": [] + }, + "sender": { + "id": 1373, + "login": "tinita", + "login_name": "", + "source_id": 0, + "full_name": "Tina Müller", + "email": "tinita@noreply.src.opensuse.org", + "avatar_url": "https://src.opensuse.org/avatars/a7cbbf47205685e65899495b73778f47a153fc03b4efbdf2b30e1b11ae742112", + "html_url": "https://src.opensuse.org/tinita", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-26T22:33:36+01:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 1, + "username": "tinita" + }, + "commit_id": "", + "review": null +} + diff --git a/tests/data/gitea-amqp/minimal-payload-gitea-review-requested.json b/tests/data/gitea-amqp/minimal-payload-gitea-review-requested.json new file mode 100644 index 00000000..b2c031e2 --- /dev/null +++ b/tests/data/gitea-amqp/minimal-payload-gitea-review-requested.json @@ -0,0 +1,26 @@ +{ + "action": "review_requested", + "pull_request": { + "id": 1234, + "url": "https://src.opensuse.org.de/api/v1/repos/owner/reponame/pulls/1234", + "html_url": "https://src.opensuse.org/owner/reponame/pulls/1234", + "state": "open", + "head": { + "label": "owner:testhook", + "ref": "testhook", + "sha": "04a3f669ea13a4aa7cbd4569f578a66f7403c43d", + "repo": { + "name": "reponame", + "full_name": "owner/reponame", + "clone_url": "https://src.opensuse.org/owner/reponame.git" + } + } + }, + "repository": { + "url": "https://src.opensuse.org/api/v1/repos/owner/reponame", + "html_url": "https://src.opensuse.org/owner/reponame" + }, + "requested_reviewer": { + "username": "qam-openqa" + } +} diff --git a/tests/test_amqp.py b/tests/test_amqp.py new file mode 100644 index 00000000..3505e082 --- /dev/null +++ b/tests/test_amqp.py @@ -0,0 +1,170 @@ +import os.path +import importlib.machinery +from argparse import Namespace +from unittest.mock import MagicMock, call, patch + +rootpath = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + +loader = importlib.machinery.SourceFileLoader( + "openqa", rootpath + "/amqp-listen-gitea.py" +) +spec = importlib.util.spec_from_loader(loader.name, loader) +openqa = importlib.util.module_from_spec(spec) +loader.exec_module(openqa) + +def args_factory(): + args = Namespace() + args.myself = 'qam-openqa' + args.verbose = 1 + args.openqa_host = 'https://openqa.example' + return args + + +def mocked_create_openqa_job(args, job_params): + return { 'foo': 'bar' } + + +def mocked_openqa_schedule(args, params): + return 'https://openqa.opensuse.org/tests/123456' + + +def mocked_openqa_cli(host, subcommand, cmds, dry_run=False): + output = """ + {"count":6,"failed":[],"ids":[5335402,5335403,5335404,5335405,5335406,5335407],"scheduled_product_id":515537} + 6 jobs have been created: + - https://openqa.opensuse.org/tests/5335402 + - https://openqa.opensuse.org/tests/5335403 + - https://openqa.opensuse.org/tests/5335404 + - https://openqa.opensuse.org/tests/5335405 + - https://openqa.opensuse.org/tests/5335406 + - https://openqa.opensuse.org/tests/5335407 + """ + return output + + +def mocked_gitea_post_status(job_params, job_url): + pass + +def mocked_fetch_url(url, request_type="text"): + return b'' + + +def mocked_request_post(url, headers, payload): + pass + + +data = { + 'requested_reviewer': { 'username': 'qam-openqa' }, + 'pull_request': { + 'id': '23', + 'html_url': 'https://src.opensuse.org/owner/reponame/pulls/1234', + 'head': { + 'sha': 'c0ffee', + 'ref': 'branch', + 'label': 'pr_user:branch', + 'repo': { + 'clone_url': 'https://src.opensuse.org/owner/reponame.git', + 'name': 'reponame', + } + } + }, + 'repository': { + 'url': 'https://src.opensuse.org/api/v1/repos/owner/reponame', + 'html_url': 'https://src.opensuse.org/owner/reponame', + }, +} + +job_params = { + 'id': '23', + 'sha': 'c0ffee', + 'label': 'pr_user:branch', + 'clone_url': 'https://src.opensuse.org/owner/reponame.git', + 'branch': 'branch', + 'pr_html_url': 'https://src.opensuse.org/owner/reponame/pulls/1234', + 'repo_name': 'reponame', + 'repo_api_url': 'https://src.opensuse.org/api/v1/repos/owner/reponame', + 'repo_html_url': 'https://src.opensuse.org/owner/reponame' +} + + +class TestAMQP: + + + def test_nothing_todo(mock_amqp): + args = args_factory() + data = { + 'requested_reviewer': { 'username': 'someone-else' } + } + openqa.handle_review_request(data, args) + #openqa.create_openqa_job_params.assert_not_called() + + + def test_schedule_job(mock_amqp): + args = args_factory() + openqa.openqa_cli = MagicMock(side_effect=mocked_openqa_cli) + openqa.fetch_url = MagicMock(side_effect=mocked_fetch_url) + job_url = openqa.openqa_schedule(args, {'webhook_id': 'gitea:pr:42', 'foo': 'bar'}) + cmd_args = [ + '--param-file', + 'SCENARIO_DEFINITIONS_YAML=/tmp/distri-openqa-scenario.yaml', + 'VERSION=Tumbleweed', + 'DISTRI=openqa', + 'FLAVOR=dev', + 'ARCH=x86_64', + 'HDD_1=opensuse-Tumbleweed-x86_64-20250920-minimalx@uefi.qcow2', + 'webhook_id=gitea:pr:42', + 'foo=bar', + ] + openqa.openqa_cli.assert_called_once_with(args.openqa_host, 'schedule', cmd_args, False) + print(job_url) + assert(job_url == 'https://openqa.opensuse.org/tests/5335402') + + + def test_gitea_post_status(mock_amqp): + openqa.request_post = MagicMock(side_effect=mocked_request_post) + os.environ["GITEA_TOKEN"] = "abcdef" + openqa.gitea_post_status(job_params, 'https://openqa.example') + openqa.request_post.assert_called_once_with( + 'https://src.opensuse.org/api/v1/repos/owner/reponame/statuses/c0ffee', + { + 'Accept': 'application/json', + 'Authorization': 'token abcdef', + 'User-Agent': 'amqp-listen-gitea.py (https://github.com/os-autoinst/scripts)', + }, + { + 'context': 'qam-openqa', + 'description': 'openQA check', + 'state': 'pending', + 'target_url': 'https://openqa.example', + } + ) + + def test_create_openqa_job_params(mock_amqp): + args = args_factory() + openqa.openqa_schedule = MagicMock(side_effect=mocked_openqa_schedule) + openqa.gitea_post_status = MagicMock(side_effect=mocked_gitea_post_status) + openqa.handle_review_request(data, args) + openqa.openqa_schedule.assert_called_once_with(args, { + 'BUILD': 'reponame#c0ffee', + 'CASEDIR': 'https://src.opensuse.org/owner/reponame.git#c0ffee', + '_GROUP_ID': '0', + 'PRIO': '100', + 'NEEDLES_DIR': '%%CASEDIR%%/needles', + 'SCENARIO_DEFINITIONS_YAML_FILE': 'https://src.opensuse.org/owner/reponame/raw/branch/c0ffee/scenario-definitions.yaml', + 'CI_TARGET_URL': 'https://openqa.example', + 'GITEA_REPO': 'reponame', + 'GITEA_SHA': 'c0ffee', + 'GITEA_STATUSES_URL': 'https://src.opensuse.org/api/v1/repos/owner/reponame/statuses/c0ffee', + 'GITEA_PR_URL': 'https://src.opensuse.org/owner/reponame/pulls/1234', + 'webhook_id': 'gitea:pr:23', + }) + openqa.gitea_post_status.assert_called_once_with(job_params, 'https://openqa.opensuse.org/tests/123456') + + + def test_handle_review_request(mock_amqp): + args = args_factory() + openqa.create_openqa_job_params = MagicMock(side_effect=mocked_create_openqa_job) + openqa.handle_review_request(data, args) + openqa.create_openqa_job_params.assert_called_once_with(args, job_params) + +