From 832c340558223e1836cd2ac672157a49651819bb Mon Sep 17 00:00:00 2001 From: Ben Hearsum Date: Thu, 27 Nov 2025 13:14:10 -0500 Subject: [PATCH] feat: rebuild PR action task definitions correctly in CoT verification code Without this we end up with differences in metadata, tags, routes, scopes, and sometimes the env, eg: ``` ["[('change',\n" " 'metadata.owner',\n" " ('action@noreply.mozilla.org', 'pr-action@noreply.mozilla.org')),\n" " ('change', 'metadata.name', ('Action: Train', 'PR action: Train')),\n" " ('change',\n" " 'metadata.description',\n" " ('Initiate part or all of the training pipeline\\n'\n" " '\\n'\n" " 'Action triggered by clientID '\n" " '`mozilla-auth0/ad|Mozilla-LDAP|bhearsum/cli-KDQzKW`\\n',\n" " 'Initiate part or all of the training pipeline\\n'\n" " 'PR action triggered by clientID '\n" " '`mozilla-auth0/ad|Mozilla-LDAP|bhearsum/cli-KDQzKW`\\n')),\n" " ('change',\n" " 'tags.createdForUser',\n" " ('action@noreply.mozilla.org', 'pr-action@noreply.mozilla.org')),\n" " ('remove',\n" " 'routes',\n" ' [(2,\n' ' ' "'index.translations.v2.translations.revision.436b91ba4ef7f5aaf7c619a9218c3c9ea0fa63d9.taskgraph.actions.Ilsl2Xq4SNivSjs4WQOEBg')]),\n" " ('change',\n" " ['scopes', 0],\n" " ('assume:repo:github.com/bhearsum/translations:action:train',\n" " 'assume:repo:github.com/mozilla/translations:pr-action:train')),\n" " ('change',\n" " 'payload.env.TRANSLATIONS_BASE_REPOSITORY',\n" " ('https://github.com/bhearsum/translations',\n" " 'https://github.com/mozilla/translations')),\n" " ('change', 'payload.env.TRANSLATIONS_BASE_REF', ('push-plrwuypxlpum', " "'main')),\n" " ('change', 'extra.tasks_for', ('action', 'pr-action'))]"] ``` --- src/scriptworker/cot/verify.py | 23 +- tests/data/cotv4/pr_action_github.json | 99 +++++ tests/data/cotv4/pr_action_retrigger.json | 135 +++++++ .../cotv4/pr_action_retrigger_context.json | 75 ++++ .../cotv4/pr_action_retrigger_template.json | 362 ++++++++++++++++++ tests/test_cot_verify.py | 57 ++- 6 files changed, 735 insertions(+), 16 deletions(-) create mode 100644 tests/data/cotv4/pr_action_github.json create mode 100644 tests/data/cotv4/pr_action_retrigger.json create mode 100644 tests/data/cotv4/pr_action_retrigger_context.json create mode 100644 tests/data/cotv4/pr_action_retrigger_template.json diff --git a/src/scriptworker/cot/verify.py b/src/scriptworker/cot/verify.py index afc4452cc..6eeb5f7c2 100644 --- a/src/scriptworker/cot/verify.py +++ b/src/scriptworker/cot/verify.py @@ -1340,7 +1340,7 @@ async def populate_jsone_context(chain, parent_link, decision_link, tasks_for): jsone_context.update(await _get_additional_github_releases_jsone_context(decision_link)) elif tasks_for == "cron": jsone_context.update(await _get_additional_git_cron_jsone_context(decision_link)) - elif tasks_for == "action": + elif tasks_for in ("action", "pr-action"): jsone_context.update(await _get_additional_git_action_jsone_context(decision_link, parent_link)) elif tasks_for in ("github-pull-request", "github-pull-request-untrusted"): jsone_context.update(await _get_additional_github_pull_request_jsone_context(decision_link)) @@ -1446,7 +1446,7 @@ def _get_action_from_actions_json(all_actions, callback_name): raise CoTError("No action with {} callback found.".format(callback_name)) -def _wrap_action_hook_with_let(tmpl, action_perm): +def _wrap_action_hook_with_let(tmpl, action_perm, tasks_for): """Construct the hook task template body. Given the content of .taskcluster.yml, construct the task template that @@ -1457,14 +1457,14 @@ def _wrap_action_hook_with_let(tmpl, action_perm): """ return { "$let": { - "tasks_for": "action", + "tasks_for": tasks_for, "action": { "name": "${payload.decision.action.name}", "title": "${payload.decision.action.title}", "description": "${payload.decision.action.description}", "taskGroupId": "${payload.decision.action.taskGroupId}", "symbol": "${payload.decision.action.symbol}", - "repo_scope": "assume:repo:${payload.decision.repository.url[8:]}:action:" + action_perm, + "repo_scope": "assume:repo:${payload.decision.repository.url[8:]}:" + tasks_for + ":" + action_perm, "action_perm": action_perm, "cb_name": "${payload.decision.action.cb_name}", }, @@ -1502,15 +1502,14 @@ def _get_action_perm(action_defn): return action_perm -async def get_action_context_and_template(chain, parent_link, decision_link): +async def get_action_context_and_template(chain, parent_link, decision_link, tasks_for): """Get the appropriate json-e context and template for an action task. Args: chain (ChainOfTrust): the chain of trust. parent_link (LinkOfTrust): the parent link to test. decision_link (LinkOfTrust): the parent link's decision task link. - tasks_for (str): the reason the parent link was created (cron, - hg-push, action) + tasks_for (str): the reason the parent link was created (action or pr-action) Returns: (dict, dict): the json-e context and template. @@ -1520,7 +1519,7 @@ async def get_action_context_and_template(chain, parent_link, decision_link): all_actions = load_json_or_yaml(actions_path, is_path=True)["actions"] action_name = get_action_callback_name(parent_link.task) action_defn = _get_action_from_actions_json(all_actions, action_name) - jsone_context = await populate_jsone_context(chain, parent_link, decision_link, "action") + jsone_context = await populate_jsone_context(chain, parent_link, decision_link, tasks_for) if action_defn.get("kind") != "hook": raise CoTError( @@ -1530,7 +1529,7 @@ async def get_action_context_and_template(chain, parent_link, decision_link): # action-hook. in_tree_tmpl = await get_in_tree_template(decision_link) action_perm = _get_action_perm(action_defn) - tmpl = _wrap_action_hook_with_let(in_tree_tmpl, action_perm) + tmpl = _wrap_action_hook_with_let(in_tree_tmpl, action_perm, tasks_for) # define the JSON-e context with which the hook's task template was # rendered, defined at @@ -1562,8 +1561,8 @@ async def get_jsone_context_and_template(chain, parent_link, decision_link, task (dict, dict): the json-e context and template. """ - if tasks_for == "action": - jsone_context, tmpl = await get_action_context_and_template(chain, parent_link, decision_link) + if tasks_for in ("action", "pr-action"): + jsone_context, tmpl = await get_action_context_and_template(chain, parent_link, decision_link, tasks_for) else: tmpl = await get_in_tree_template(decision_link) jsone_context = await populate_jsone_context(chain, parent_link, decision_link, tasks_for) @@ -1598,7 +1597,7 @@ async def verify_parent_task_definition(chain, parent_link): tasks_for = get_and_check_tasks_for(chain.context, parent_link.task, "{} {}: ".format(parent_link.name, parent_link.task_id)) jsone_context, tmpl = await get_jsone_context_and_template(chain, parent_link, decision_link, tasks_for) rebuilt_definitions = jsone.render(tmpl, jsone_context) - if tasks_for == "action": + if tasks_for in ("action", "pr-action"): check_and_update_action_task_group_id(parent_link, decision_link, rebuilt_definitions) except jsone.JSONTemplateError as e: log.exception("JSON-e error while rebuilding {} task definition!".format(parent_link.name)) diff --git a/tests/data/cotv4/pr_action_github.json b/tests/data/cotv4/pr_action_github.json new file mode 100644 index 000000000..0b6565e60 --- /dev/null +++ b/tests/data/cotv4/pr_action_github.json @@ -0,0 +1,99 @@ +{ + "provisionerId": "aws-provisioner-v1", + "workerType": "xpi-1-decision", + "schedulerId": "xpi-level-1", + "taskGroupId": "decision_task_id", + "dependencies": [], + "requires": "all-completed", + "routes": [ + "checks", + "tc-treeherder.v2.xpi-manifest.1f444d8465ad7a564b230e49ccb37171271ac02b" + ], + "priority": "lowest", + "retries": 5, + "created": "2019-10-21T23:36:07.490Z", + "deadline": "2019-10-22T23:36:07.490Z", + "expires": "2020-10-20T23:36:08.490Z", + "scopes": [ + "assume:repo:github.com/escapewindow/xpi-manifest:pr-action:release-promotion" + ], + "payload": { + "env": { + "XPI_BASE_REPOSITORY": "https://github.com/escapewindow/xpi-manifest", + "XPI_HEAD_REPOSITORY": "https://github.com/escapewindow/xpi-manifest", + "XPI_HEAD_REF": "refs/heads/master", + "XPI_HEAD_REV": "1f444d8465ad7a564b230e49ccb37171271ac02b", + "XPI_REPOSITORY_TYPE": "git", + "TASKGRAPH_BASE_REPOSITORY": "https://hg.mozilla.org/ci/taskgraph", + "TASKGRAPH_HEAD_REPOSITORY": "https://hg.mozilla.org/ci/taskgraph-try", + "TASKGRAPH_HEAD_REV": "d6f5998ed6ea2ffbb40d68451fa3b40c37fe1f62", + "TASKGRAPH_REPOSITORY_TYPE": "hg", + "REPOSITORIES": "{\"taskgraph\":\"Taskgraph\",\"xpi\":\"XPI Manifest\"}", + "HG_STORE_PATH": "/builds/worker/checkouts/hg-store", + "ACTION_TASK_GROUP_ID": "f3V--kXhQnOHjv7y7fTDFA", + "ACTION_TASK_ID": "null", + "ACTION_INPUT": "{\"build_number\":1,\"do_not_optimize\":[],\"previous_graph_ids\":[],\"rebuild_kinds\":[],\"release_promotion_flavor\":\"build\",\"version\":\"\",\"xpi_name\":\"multipreffer\"}", + "ACTION_CALLBACK": "release-promotion" + }, + "features": { + "taskclusterProxy": true, + "chainOfTrust": true + }, + "image": "mozillareleases/taskgraph:decision-bd477b55732fc5f5d55a78e6162355af8bc81805b415a9ea8dbe42c020f840db", + "maxRunTime": 1800, + "command": [ + "/usr/local/bin/run-task", + "--xpi-checkout=/builds/worker/checkouts/src", + "--taskgraph-checkout=/builds/worker/checkouts/taskgraph", + "--task-cwd=/builds/worker/checkouts/src", + "--", + "bash", + "-cx", + "PIP_IGNORE_INSTALLED=0 pip install --user /builds/worker/checkouts/taskgraph && cd /builds/worker/checkouts/src && ln -s /builds/worker/artifacts artifacts && ~/.local/bin/taskgraph action-callback\n" + ], + "artifacts": { + "public": { + "type": "directory", + "path": "/builds/worker/artifacts", + "expires": "2020-10-20T23:36:07.490Z" + } + } + }, + "metadata": { + "owner": "pr-action@noreply.mozilla.org", + "source": "https://github.com/escapewindow/xpi-manifest/raw/1f444d8465ad7a564b230e49ccb37171271ac02b/.taskcluster.yml", + "name": "PR action: Promote a XPI", + "description": "Promote a XPI." + }, + "tags": { + "kind": "action-callback" + }, + "extra": { + "treeherder": { + "machine": { + "platform": "gecko-decision" + }, + "groupName": "action-callback", + "groupSymbol": "AC", + "symbol": "build_multipreffer" + }, + "parent": "f3V--kXhQnOHjv7y7fTDFA", + "action": { + "name": "release-promotion", + "context": { + "taskGroupId": "decision_task_id", + "taskId": null, + "input": { + "build_number": 1, + "do_not_optimize": [], + "previous_graph_ids": [], + "rebuild_kinds": [], + "release_promotion_flavor": "build", + "version": "", + "xpi_name": "multipreffer" + } + } + }, + "tasks_for": "pr-action" + } +} diff --git a/tests/data/cotv4/pr_action_retrigger.json b/tests/data/cotv4/pr_action_retrigger.json new file mode 100644 index 000000000..e6044cdaa --- /dev/null +++ b/tests/data/cotv4/pr_action_retrigger.json @@ -0,0 +1,135 @@ +{ + "provisionerId": "aws-provisioner-v1", + "workerType": "gecko-1-decision", + "schedulerId": "gecko-level-1", + "taskGroupId": "LpCJV9wUQHSAm5SHyW4Olw", + "dependencies": [], + "requires": "all-completed", + "routes": [ + "tc-treeherder.v2.try.c5429a8112e41dfabe22da0d7d0876fe05a17e67.323126", + "notify.email.taskcluster-notifications+action-task@mozilla.com.on-failed", + "notify.email.taskcluster-notifications+action-task@mozilla.com.on-exception", + "index.gecko.v2.try.pushlog-id.323126.actions.E93EolXNSRuV2gOy7yMZEA" + ], + "priority": "very-low", + "retries": 5, + "created": "2018-12-19T19:26:57.706Z", + "deadline": "2018-12-20T19:26:57.706Z", + "expires": "2019-12-19T19:26:58.706Z", + "scopes": [ + "assume:repo:hg.mozilla.org/try:pr-action:generic" + ], + "payload": { + "env": { + "GECKO_BASE_REPOSITORY": "https://hg.mozilla.org/mozilla-unified", + "GECKO_HEAD_REPOSITORY": "https://hg.mozilla.org/try", + "GECKO_HEAD_REF": "c5429a8112e41dfabe22da0d7d0876fe05a17e67", + "GECKO_HEAD_REV": "c5429a8112e41dfabe22da0d7d0876fe05a17e67", + "HG_STORE_PATH": "/builds/worker/checkouts/hg-store", + "TASKCLUSTER_CACHES": "/builds/worker/checkouts", + "ACTION_TASK_GROUP_ID": "LpCJV9wUQHSAm5SHyW4Olw", + "ACTION_TASK_ID": "\"KkDeSaLFSq-OFo1QGc-hGQ\"", + "ACTION_INPUT": "{\"downstream\":false,\"times\":1}", + "ACTION_CALLBACK": "retrigger", + "ACTION_PARAMETERS": "{\"app_version\":\"65.0\",\"base_repository\":\"https://hg.mozilla.org/mozilla-unified\",\"build_date\":1545243227,\"build_number\":1,\"do_not_optimize\":[],\"existing_tasks\":{},\"filters\":[\"target_tasks_method\"],\"head_ref\":\"c5429a8112e41dfabe22da0d7d0876fe05a17e67\",\"head_repository\":\"https://hg.mozilla.org/try\",\"head_rev\":\"c5429a8112e41dfabe22da0d7d0876fe05a17e67\",\"hg_branch\":\"default\\n\",\"level\":\"1\",\"message\":\" \",\"moz_build_date\":\"20181219181347\",\"next_version\":null,\"optimize_target_tasks\":true,\"owner\":\"asasaki@mozilla.com\",\"project\":\"try\",\"pushdate\":1545243227,\"pushlog_id\":\"323126\",\"release_enable_emefree\":false,\"release_enable_partners\":false,\"release_eta\":\"\",\"release_history\":{},\"release_partner_build_number\":1,\"release_partner_config\":{},\"release_partners\":[],\"release_product\":null,\"release_type\":\"beta\",\"required_signoffs\":[],\"signoff_urls\":{},\"target_tasks_method\":\"staging_release_builds\",\"try_mode\":null,\"try_options\":null,\"try_task_config\":null,\"version\":\"65.0b9\"}" + }, + "cache": { + "level-1-checkouts-sparse-v2": "/builds/worker/checkouts" + }, + "features": { + "taskclusterProxy": true, + "chainOfTrust": true + }, + "image": "taskcluster/decision:2.1.0@sha256:6db3b697d7a3c7aba440d72f04199331b872111cefff57206b8b8b1d53230360", + "maxRunTime": 1800, + "command": [ + "/builds/worker/bin/run-task", + "--vcs-checkout=/builds/worker/checkouts/gecko", + "--sparse-profile=build/sparse-profiles/taskgraph", + "--", + "bash", + "-cx", + "cd /builds/worker/checkouts/gecko && ln -s /builds/worker/artifacts artifacts && ./mach --log-no-times taskgraph action-callback\n" + ], + "artifacts": { + "public": { + "type": "directory", + "path": "/builds/worker/artifacts", + "expires": "2019-12-19T19:26:57.706Z" + } + } + }, + "metadata": { + "owner": "pr-action@noreply.mozilla.org", + "source": "https://hg.mozilla.org/try/raw-file/c5429a8112e41dfabe22da0d7d0876fe05a17e67/.taskcluster.yml", + "name": "PR action: Retrigger", + "description": "Create a clone of the task.\nPR action triggered by clientID `mozilla-auth0/ad|Mozilla-LDAP|test`" + }, + "tags": { + "createdForUser": "pr-action@noreply.mozilla.org", + "kind": "action-callback" + }, + "extra": { + "treeherder": { + "machine": { + "platform": "gecko-decision" + }, + "groupName": "action-callback", + "groupSymbol": "AC", + "symbol": "rt" + }, + "parent": "LpCJV9wUQHSAm5SHyW4Olw", + "action": { + "name": "retrigger", + "context": { + "taskGroupId": "LpCJV9wUQHSAm5SHyW4Olw", + "taskId": "KkDeSaLFSq-OFo1QGc-hGQ", + "input": { + "downstream": false, + "times": 1 + }, + "parameters": { + "app_version": "65.0", + "base_repository": "https://hg.mozilla.org/mozilla-unified", + "build_date": 1545243227, + "build_number": 1, + "do_not_optimize": [], + "existing_tasks": {}, + "filters": [ + "target_tasks_method" + ], + "head_ref": "c5429a8112e41dfabe22da0d7d0876fe05a17e67", + "head_repository": "https://hg.mozilla.org/try", + "head_rev": "c5429a8112e41dfabe22da0d7d0876fe05a17e67", + "hg_branch": "default\n", + "level": "1", + "message": " ", + "moz_build_date": "20181219181347", + "next_version": null, + "optimize_target_tasks": true, + "owner": "asasaki@mozilla.com", + "project": "try", + "pushdate": 1545243227, + "pushlog_id": "323126", + "release_enable_emefree": false, + "release_enable_partners": false, + "release_eta": "", + "release_history": {}, + "release_partner_build_number": 1, + "release_partner_config": {}, + "release_partners": [], + "release_product": null, + "release_type": "beta", + "required_signoffs": [], + "signoff_urls": {}, + "target_tasks_method": "staging_release_builds", + "try_mode": null, + "try_options": null, + "try_task_config": null, + "version": "65.0b9" + } + } + }, + "tasks_for": "pr-action" + } +} diff --git a/tests/data/cotv4/pr_action_retrigger_context.json b/tests/data/cotv4/pr_action_retrigger_context.json new file mode 100644 index 000000000..c798e02b2 --- /dev/null +++ b/tests/data/cotv4/pr_action_retrigger_context.json @@ -0,0 +1,75 @@ +{ + "now": "2018-12-19T19:26:57.706Z", + "payload": { + "decision": { + "action": { + "cb_name": "retrigger", + "description": "Create a clone of the task.", + "name": "retrigger", + "symbol": "rt", + "taskGroupId": "LpCJV9wUQHSAm5SHyW4Olw", + "title": "Retrigger" + }, + "parameters": { + "app_version": "65.0", + "base_repository": "https://hg.mozilla.org/mozilla-unified", + "build_date": 1545243227, + "build_number": 1, + "do_not_optimize": [], + "existing_tasks": {}, + "filters": [ + "target_tasks_method" + ], + "head_ref": "c5429a8112e41dfabe22da0d7d0876fe05a17e67", + "head_repository": "https://hg.mozilla.org/try", + "head_rev": "c5429a8112e41dfabe22da0d7d0876fe05a17e67", + "hg_branch": "default\n", + "level": "1", + "message": " ", + "moz_build_date": "20181219181347", + "next_version": null, + "optimize_target_tasks": true, + "owner": "asasaki@mozilla.com", + "project": "try", + "pushdate": 1545243227, + "pushlog_id": "323126", + "release_enable_emefree": false, + "release_enable_partners": false, + "release_eta": "", + "release_history": {}, + "release_partner_build_number": 1, + "release_partner_config": {}, + "release_partners": [], + "release_product": null, + "release_type": "beta", + "required_signoffs": [], + "signoff_urls": {}, + "target_tasks_method": "staging_release_builds", + "try_mode": null, + "try_options": null, + "try_task_config": null, + "version": "65.0b9" + }, + "push": { + "owner": "mozilla-taskcluster-maintenance@mozilla.com", + "pushlog_id": "323126", + "revision": "c5429a8112e41dfabe22da0d7d0876fe05a17e67" + }, + "repository": { + "level": "1", + "project": "try", + "url": "https://hg.mozilla.org/try" + } + }, + "user": { + "input": { + "downstream": false, + "times": 1 + }, + "taskGroupId": "LpCJV9wUQHSAm5SHyW4Olw", + "taskId": "KkDeSaLFSq-OFo1QGc-hGQ" + } + }, + "taskId": "NdzxKw8bS5Sw5DRhoiM14w", + "clientId": null +} diff --git a/tests/data/cotv4/pr_action_retrigger_template.json b/tests/data/cotv4/pr_action_retrigger_template.json new file mode 100644 index 000000000..3a449d547 --- /dev/null +++ b/tests/data/cotv4/pr_action_retrigger_template.json @@ -0,0 +1,362 @@ +{ + "$let": { + "action": { + "action_perm": "generic", + "cb_name": "${payload.decision.action.cb_name}", + "description": "${payload.decision.action.description}", + "name": "${payload.decision.action.name}", + "repo_scope": "assume:repo:${payload.decision.repository.url[8:]}:pr-action:generic", + "symbol": "${payload.decision.action.symbol}", + "taskGroupId": "${payload.decision.action.taskGroupId}", + "title": "${payload.decision.action.title}" + }, + "input": { + "$eval": "payload.user.input" + }, + "ownTaskId": { + "$eval": "taskId" + }, + "parameters": { + "$eval": "payload.decision['parameters']" + }, + "push": { + "$eval": "payload.decision.push" + }, + "repository": { + "$eval": "payload.decision.repository" + }, + "taskGroupId": { + "$eval": "payload.user.taskGroupId" + }, + "taskId": { + "$eval": "payload.user.taskId" + }, + "tasks_for": "pr-action" + }, + "in": { + "tasks": [ + { + "$if": "tasks_for in [\"hg-push\", \"action\", \"cron\"]", + "then": { + "$let": { + "ownerEmail": { + "$if": "\"@\" in push.owner", + "else": "${push.owner}@noreply.mozilla.org", + "then": "${push.owner}" + }, + "repoUrl": { + "$if": "repository.url[-1] == \"/\"", + "else": { + "$eval": "repository.url" + }, + "then": { + "$eval": "repository.url[:-1]" + } + } + }, + "in": { + "created": { + "$fromNow": "" + }, + "deadline": { + "$fromNow": "1 day" + }, + "dependencies": [], + "expires": { + "$fromNow": "1 year 1 second" + }, + "extra": { + "$merge": [ + { + "treeherder": { + "$merge": [ + { + "machine": { + "platform": "gecko-decision" + } + }, + { + "$if": "tasks_for == \"hg-push\"", + "else": { + "$if": "tasks_for == \"action\"", + "else": { + "groupSymbol": "cron", + "symbol": "${cron.job_symbol}" + }, + "then": { + "groupName": "action-callback", + "groupSymbol": "AC", + "symbol": "${action.symbol}" + } + }, + "then": { + "symbol": "D" + } + } + ] + } + }, + { + "$if": "tasks_for == \"action\"", + "then": { + "action": { + "context": { + "input": { + "$eval": "input" + }, + "parameters": { + "$eval": "parameters" + }, + "taskGroupId": "${action.taskGroupId}", + "taskId": { + "$eval": "taskId" + } + }, + "name": "${action.name}" + }, + "parent": "${action.taskGroupId}" + } + }, + { + "$if": "tasks_for == \"cron\"", + "then": { + "cron": { + "$json": { + "$eval": "cron" + } + } + } + }, + { + "tasks_for": "${tasks_for}" + }, + { + "$if": "tasks_for == \"hg-push\" && repository.project == \"try\"", + "then": { + "notify": { + "email": { + "content": "Your try push has been submitted. It's the best! Use the link to view the status of your jobs.", + "link": { + "href": "https://treeherder.mozilla.org/#/jobs?repo=${repository.project}&revision=${push.revision}", + "text": "Treeherder Jobs" + }, + "subject": "Thank you for your try submission of ${push.revision}. It's the best!" + } + } + } + } + ] + }, + "metadata": { + "$merge": [ + { + "owner": "${ownerEmail}", + "source": "${repoUrl}/raw-file/${push.revision}/.taskcluster.yml" + }, + { + "$if": "tasks_for == \"hg-push\"", + "else": { + "$if": "tasks_for == \"action\"", + "else": { + "description": "Created by a [cron task](https://tools.taskcluster.net/tasks/${cron.task_id})", + "name": "Decision Task for cron job ${cron.job_name}" + }, + "then": { + "description": "${action.description}", + "name": "Action: ${action.title}" + } + }, + "then": { + "description": "The task that creates all of the other tasks in the task graph", + "name": "Gecko Decision Task" + } + } + ] + }, + "payload": { + "artifacts": { + "public": { + "expires": { + "$fromNow": "1 year" + }, + "path": "/builds/worker/artifacts", + "type": "directory" + } + }, + "cache": { + "level-${repository.level}-checkouts-sparse-v2": "/builds/worker/checkouts" + }, + "command": [ + "/builds/worker/bin/run-task", + "--vcs-checkout=/builds/worker/checkouts/gecko", + "--sparse-profile=build/sparse-profiles/taskgraph", + "--", + "bash", + "-cx", + { + "$let": { + "extraArgs": { + "$if": "tasks_for == \"cron\"", + "else": "", + "then": "${cron.quoted_args}" + } + }, + "in": { + "$if": "tasks_for == \"action\"", + "else": "cd /builds/worker/checkouts/gecko && ln -s /builds/worker/artifacts artifacts && ./mach --log-no-times taskgraph decision --pushlog-id='${push.pushlog_id}' --pushdate='${push.pushdate}' --project='${repository.project}' --message=\"$GECKO_COMMIT_MSG\" --owner='${ownerEmail}' --level='${repository.level}' --base-repository=\"$GECKO_BASE_REPOSITORY\" --head-repository=\"$GECKO_HEAD_REPOSITORY\" --head-ref=\"$GECKO_HEAD_REF\" --head-rev=\"$GECKO_HEAD_REV\" ${extraArgs}\n", + "then": "cd /builds/worker/checkouts/gecko && ln -s /builds/worker/artifacts artifacts && ./mach --log-no-times taskgraph action-callback\n" + } + } + ], + "env": { + "$merge": [ + { + "GECKO_BASE_REPOSITORY": "https://hg.mozilla.org/mozilla-unified", + "GECKO_COMMIT_MSG": { + "$if": "tasks_for != \"action\"", + "then": "${push.comment}" + }, + "GECKO_HEAD_REF": "${push.revision}", + "GECKO_HEAD_REPOSITORY": "${repoUrl}", + "GECKO_HEAD_REV": "${push.revision}", + "HG_STORE_PATH": "/builds/worker/checkouts/hg-store", + "TASKCLUSTER_CACHES": "/builds/worker/checkouts" + }, + { + "$if": "tasks_for == \"action\"", + "then": { + "ACTION_CALLBACK": "${action.cb_name}", + "ACTION_INPUT": { + "$json": { + "$eval": "input" + } + }, + "ACTION_PARAMETERS": { + "$json": { + "$eval": "parameters" + } + }, + "ACTION_TASK_GROUP_ID": "${action.taskGroupId}", + "ACTION_TASK_ID": { + "$json": { + "$eval": "taskId" + } + } + } + } + ] + }, + "features": { + "chainOfTrust": true, + "taskclusterProxy": true + }, + "image": "taskcluster/decision:2.1.0@sha256:6db3b697d7a3c7aba440d72f04199331b872111cefff57206b8b8b1d53230360", + "maxRunTime": 1800 + }, + "priority": { + "$if": "tasks_for == 'cron'", + "else": { + "$if": "tasks_for == 'action'", + "else": "lowest", + "then": "very-low" + }, + "then": "low" + }, + "provisionerId": "aws-provisioner-v1", + "requires": "all-completed", + "retries": 5, + "routes": { + "$flatten": [ + "tc-treeherder.v2.${repository.project}.${push.revision}.${push.pushlog_id}", + { + "$if": "tasks_for == \"hg-push\"", + "else": { + "$if": "tasks_for == \"action\"", + "else": [ + "index.gecko.v2.${repository.project}.latest.taskgraph.decision-${cron.job_name}", + "index.gecko.v2.${repository.project}.revision.${push.revision}.taskgraph.decision-${cron.job_name}", + "index.gecko.v2.${repository.project}.pushlog-id.${push.pushlog_id}.decision-${cron.job_name}", + "index.gecko.v2.${repository.project}.revision.${push.revision}.cron.${as_slugid(\"decision\")}", + "index.gecko.v2.${repository.project}.latest.firefox.decision-${cron.job_name}" + ], + "then": [ + "notify.email.taskcluster-notifications+action-task@mozilla.com.on-failed", + "notify.email.taskcluster-notifications+action-task@mozilla.com.on-exception", + "index.gecko.v2.${repository.project}.pushlog-id.${push.pushlog_id}.actions.${ownTaskId}" + ] + }, + "then": [ + "index.gecko.v2.${repository.project}.latest.taskgraph.decision", + "index.gecko.v2.${repository.project}.revision.${push.revision}.taskgraph.decision", + "index.gecko.v2.${repository.project}.pushlog-id.${push.pushlog_id}.decision", + "notify.email.${ownerEmail}.on-failed", + "notify.email.${ownerEmail}.on-exception", + { + "$if": "repository.project == \"try\"", + "then": "notify.email.${ownerEmail}.on-completed" + }, + "notify.email.ciduty+failedcron@mozilla.com.on-failed", + "notify.email.ciduty+exceptioncron@mozilla.com.on-exception", + "notify.email.sheriffs+failedcron@mozilla.com.on-failed", + "notify.email.sheriffs+exceptioncron@mozilla.com.on-exception", + "index.gecko.v2.${repository.project}.latest.firefox.decision", + "index.gecko.v2.${repository.project}.revision.${push.revision}.firefox.decision" + ] + } + ] + }, + "schedulerId": "gecko-level-${repository.level}", + "scopes": { + "$if": "tasks_for == \"hg-push\"", + "else": { + "$if": "tasks_for == \"action\"", + "else": [ + "assume:repo:${repoUrl[8:]}:cron:${cron.job_name}" + ], + "then": [ + "${action.repo_scope}" + ] + }, + "then": [ + "assume:repo:${repoUrl[8:]}:branch:default", + "queue:route:notify.email.${ownerEmail}.*", + "in-tree:hook-action:project-gecko/in-tree-action-${repository.level}-*" + ] + }, + "tags": { + "$if": "tasks_for == \"hg-push\"", + "else": { + "$if": "tasks_for == \"action\"", + "else": { + "$if": "tasks_for == \"cron\"", + "then": { + "kind": "cron-task" + } + }, + "then": { + "createdForUser": "${ownerEmail}", + "kind": "action-callback" + } + }, + "then": { + "createdForUser": "${ownerEmail}", + "kind": "decision-task" + } + }, + "taskGroupId": { + "$if": "tasks_for == \"action\"", + "else": "${as_slugid(\"decision\")}", + "then": "${action.taskGroupId}" + }, + "taskId": { + "$if": "tasks_for != \"action\"", + "then": "${as_slugid(\"decision\")}" + }, + "workerType": "gecko-${repository.level}-decision" + } + } + } + ], + "version": 1 + } +} diff --git a/tests/test_cot_verify.py b/tests/test_cot_verify.py index 1c03dd95c..41badde65 100644 --- a/tests/test_cot_verify.py +++ b/tests/test_cot_verify.py @@ -166,6 +166,14 @@ def github_action_link(mobile_chain): yield link +@pytest.fixture(scope="function") +def github_pr_action_link(mobile_chain): + link = cotverify.LinkOfTrust(mobile_chain.context, "action", "pr_action_task_id") + with open(os.path.join(COTV4_DIR, "pr_action_github.json")) as fh: + link.task = json.load(fh) + yield link + + @pytest.fixture(scope="function") def mobile_github_release_link(mobile_chain): decision_link = _craft_decision_link( @@ -1419,7 +1427,7 @@ async def test_get_action_context_and_template( chain.links = list(set([decision_link, link])) - result = await cotverify.get_action_context_and_template(chain, link, decision_link) + result = await cotverify.get_action_context_and_template(chain, link, decision_link, "action") log.info("result:\n{}".format(result)) with open(expected_template_path) as fh: fake_template = json.load(fh) @@ -1458,7 +1466,7 @@ def fake_get_action_from_actions_json(all_actions, callback_name): chain.links = list(set([decision_link, link])) with pytest.raises(CoTError, match="Unknown action kind .BROKEN BROKEN BROKEN"): - await cotverify.get_action_context_and_template(chain, link, decision_link) + await cotverify.get_action_context_and_template(chain, link, decision_link, "action") # verify_parent_task_definition {{{1 @@ -1653,7 +1661,7 @@ async def test_no_match_in_actions_json(chain): chain.links = list(set([decision_link, link])) with pytest.raises(CoTError, match="No action with .* callback found."): - await cotverify.get_action_context_and_template(chain, link, decision_link) + await cotverify.get_action_context_and_template(chain, link, decision_link, "action") @pytest.mark.asyncio @@ -1693,7 +1701,48 @@ async def test_unknown_action_kind(chain): chain.links = list(set([decision_link, link])) with pytest.raises(CoTError, match="Unknown action kind"): - await cotverify.get_action_context_and_template(chain, link, decision_link) + await cotverify.get_action_context_and_template(chain, link, decision_link, "action") + + +@pytest.mark.asyncio +async def test_populate_jsone_context_github_pr_action(mobile_chain, github_pr_action_link, mobile_github_push_link): + """Test that pr-action jsone context is populated correctly.""" + context = await cotverify.populate_jsone_context(mobile_chain, github_pr_action_link, mobile_github_push_link, tasks_for="pr-action") + del context["as_slugid"] + assert context == { + "now": "2019-10-21T23:36:07.490Z", + "ownTaskId": "pr_action_task_id", + "taskId": None, + "tasks_for": "pr-action", + "taskGroupId": "decision_task_id", + "input": { + "build_number": 1, + "do_not_optimize": [], + "previous_graph_ids": [], + "rebuild_kinds": [], + "release_promotion_flavor": "build", + "version": "", + "xpi_name": "multipreffer", + }, + } + + +@pytest.mark.asyncio +async def test_get_pr_action_context_and_template(mocker, mobile_chain, github_pr_action_link, mobile_github_push_link): + """Test that pr-action tasks generate correct context and template with pr-action specific values.""" + mobile_chain.context.config["min_cot_version"] = 3 + + mocker.patch.object(cotverify, "load_json_or_yaml_from_url", new=cotv4_load_url) + mocker.patch.object(swcontext, "load_json_or_yaml_from_url", new=cotv4_load_url) + mocker.patch.object(cotverify, "load_json_or_yaml", new=cotv4_load) + + mobile_chain.links = list(set([mobile_github_push_link, github_pr_action_link])) + + context, template = await cotverify.get_action_context_and_template(mobile_chain, github_pr_action_link, mobile_github_push_link, "pr-action") + + # Verify the template has pr-action specific values + assert template["$let"]["tasks_for"] == "pr-action" + assert "pr-action" in template["$let"]["action"]["repo_scope"] @pytest.mark.asyncio