mozilla-taskgraph 3.3.2__tar.gz → 3.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.pre-commit-config.yaml +2 -2
  2. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/CHANGELOG.md +6 -0
  3. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/PKG-INFO +1 -1
  4. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/pyproject.toml +3 -4
  5. mozilla_taskgraph-3.4.0/src/mozilla_taskgraph/transforms/replicate.py +241 -0
  6. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/conftest.py +1 -1
  7. mozilla_taskgraph-3.4.0/test/transforms/test_replicate.py +275 -0
  8. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/uv.lock +414 -414
  9. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.codespell-ignore-words.txt +0 -0
  10. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.github/CODEOWNERS +0 -0
  11. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.github/workflows/pre-commit-autoupdate.yml +0 -0
  12. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.github/workflows/pre-commit.yml +0 -0
  13. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.github/workflows/pypi-publish.yml +0 -0
  14. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.gitignore +0 -0
  15. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.readthedocs.yml +0 -0
  16. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.taskcluster.yml +0 -0
  17. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.yamllint +0 -0
  18. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/CODE_OF_CONDUCT.md +0 -0
  19. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/CONTRIBUTING.md +0 -0
  20. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/LICENSE +0 -0
  21. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/Makefile +0 -0
  22. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/README.md +0 -0
  23. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/docs/actions/index.rst +0 -0
  24. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/docs/actions/release-promotion.rst +0 -0
  25. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/docs/conf.py +0 -0
  26. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/docs/index.rst +0 -0
  27. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/docs/transforms/index.rst +0 -0
  28. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/docs/transforms/scriptworker/index.rst +0 -0
  29. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/docs/transforms/scriptworker/release_artifacts.rst +0 -0
  30. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/docs/transforms/scriptworker/ship-it.rst +0 -0
  31. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/__init__.py +0 -0
  32. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/actions/__init__.py +0 -0
  33. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/actions/release_promotion.py +0 -0
  34. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/config.py +0 -0
  35. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/transforms/__init__.py +0 -0
  36. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/transforms/scriptworker/__init__.py +0 -0
  37. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/transforms/scriptworker/release_artifacts.py +0 -0
  38. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/transforms/scriptworker/shipit/__init__.py +0 -0
  39. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/transforms/scriptworker/shipit/mark_as_shipped.py +0 -0
  40. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/util/signed_artifacts.py +0 -0
  41. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/version.py +0 -0
  42. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/worker_types.py +0 -0
  43. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/config.yml +0 -0
  44. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/docker/fetch/Dockerfile +0 -0
  45. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/docker/fetch/REGISTRY +0 -0
  46. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/docker/python/Dockerfile +0 -0
  47. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/kinds/check/kind.yml +0 -0
  48. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/kinds/codecov/kind.yml +0 -0
  49. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/kinds/docker-image/kind.yml +0 -0
  50. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/kinds/fetch/kind.yml +0 -0
  51. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/kinds/release-promotion-dummy/kind.yml +0 -0
  52. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/kinds/test/kind.yml +0 -0
  53. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/mt_taskgraph/target_tasks.py +0 -0
  54. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/mt_taskgraph/transforms/test.py +0 -0
  55. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/scripts/codecov-upload.py +0 -0
  56. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/__init__.py +0 -0
  57. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/actions/__init__.py +0 -0
  58. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/actions/test_release_promotion.py +0 -0
  59. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/data/taskcluster/config.yml +0 -0
  60. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/data/testver.py +0 -0
  61. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/test_register.py +0 -0
  62. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/test_version.py +0 -0
  63. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/test_worker_types.py +0 -0
  64. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/transforms/test_scriptworker_release_artifacts.py +0 -0
  65. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/transforms/test_scriptworker_shipit.py +0 -0
  66. {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/util/test_signed_artifacts.py +0 -0
@@ -13,11 +13,11 @@ repos:
13
13
  exclude: template
14
14
  - id: check-added-large-files
15
15
  - repo: https://github.com/astral-sh/uv-pre-commit
16
- rev: 0.7.9
16
+ rev: 0.7.17
17
17
  hooks:
18
18
  - id: uv-lock
19
19
  - repo: https://github.com/astral-sh/ruff-pre-commit
20
- rev: 'v0.11.12'
20
+ rev: 'v0.12.1'
21
21
  hooks:
22
22
  - id: ruff
23
23
  args: [--fix, --exit-non-zero-on-fix]
@@ -1,3 +1,9 @@
1
+ ## 3.4.0 (2025-07-15)
2
+
3
+ ### Feat
4
+
5
+ - add transforms for cross trust domain integration tests
6
+
1
7
  ## 3.3.2 (2025-06-16)
2
8
 
3
9
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mozilla-taskgraph
3
- Version: 3.3.2
3
+ Version: 3.4.0
4
4
  Summary: Mozilla specific transforms and utilities for Taskgraph
5
5
  Project-URL: Repository, https://github.com/mozilla-releng/mozilla-taskgraph
6
6
  Project-URL: Issues, https://github.com/mozilla-releng/mozilla-taskgraph/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mozilla-taskgraph"
3
- version = "3.3.2"
3
+ version = "3.4.0"
4
4
  description = "Mozilla specific transforms and utilities for Taskgraph"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -63,7 +63,7 @@ version_provider = "pep621"
63
63
  branch = true
64
64
  source = ["src/mozilla_taskgraph/", "mozilla_taskgraph"]
65
65
 
66
- [tool.ruff]
66
+ [tool.ruff.lint]
67
67
  select = [
68
68
  "E", "W", # pycodestyle
69
69
  "F", # pyflakes
@@ -76,7 +76,6 @@ ignore = [
76
76
  "E501", # let black handle line-length
77
77
  "E741",
78
78
  ]
79
- target-version = "py37"
80
79
 
81
- [tool.ruff.isort]
80
+ [tool.ruff.lint.isort]
82
81
  known-first-party = ["mozilla_taskgraph"]
@@ -0,0 +1,241 @@
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
+
5
+ from __future__ import annotations
6
+
7
+ import os
8
+ import re
9
+ from textwrap import dedent
10
+
11
+ from requests.exceptions import HTTPError
12
+ from taskgraph.transforms.base import TransformSequence
13
+ from taskgraph.util.attributes import attrmatch
14
+ from taskgraph.util.schema import Schema
15
+ from taskgraph.util.taskcluster import (
16
+ find_task_id,
17
+ get_artifact,
18
+ get_task_definition,
19
+ )
20
+ from voluptuous import ALLOW_EXTRA, Any, Optional, Required
21
+
22
+ REPLICATE_SCHEMA = Schema(
23
+ {
24
+ Required(
25
+ "replicate",
26
+ description=dedent(
27
+ """
28
+ Configuration for the replicate transforms.
29
+ """.lstrip(),
30
+ ),
31
+ ): {
32
+ Required(
33
+ "target",
34
+ description=dedent(
35
+ """
36
+ Define which tasks to target for replication.
37
+
38
+ Each item in the list can be either:
39
+
40
+ 1. A taskId
41
+ 2. An index path that points to a single task
42
+
43
+ If any of the resolved tasks are a Decision task, targeted
44
+ tasks will be derived from the `task-graph.json` artifact.
45
+ """.lstrip()
46
+ ),
47
+ ): [str],
48
+ Optional(
49
+ "include-attrs",
50
+ description=dedent(
51
+ """
52
+ A dict of attribute key/value pairs that targeted tasks will be
53
+ filtered on. Targeted tasks must *match all* of the given
54
+ attributes or they will be ignored.
55
+
56
+ Matching is performed by the :func:`~taskgraph.util.attrmatch`
57
+ utility function.
58
+ """.lstrip(),
59
+ ),
60
+ ): {str: Any(str, [str])},
61
+ Optional(
62
+ "exclude-attrs",
63
+ description=dedent(
64
+ """
65
+ A dict of attribute key/value pairs that targeted tasks will be
66
+ filtered on. Targeted tasks must *not match any* of the given
67
+ attributes or they will be ignored.
68
+
69
+ Matching is performed by the :func:`~taskgraph.util.attrmatch`
70
+ utility function.
71
+ """.lstrip(),
72
+ ),
73
+ ): {str: Any(str, [str])},
74
+ },
75
+ },
76
+ extra=ALLOW_EXTRA,
77
+ )
78
+
79
+ TASK_ID_RE = re.compile(
80
+ r"^[A-Za-z0-9_-]{8}[Q-T][A-Za-z0-9_-][CGKOSWaeimquy26-][A-Za-z0-9_-]{10}[AQgw]$"
81
+ )
82
+
83
+ transforms = TransformSequence()
84
+ transforms.add_validate(REPLICATE_SCHEMA)
85
+
86
+
87
+ @transforms.add
88
+ def resolve_targets(config, tasks):
89
+ for task in tasks:
90
+ config = task.pop("replicate")
91
+
92
+ task_defs = []
93
+ for target in config["target"]:
94
+ if TASK_ID_RE.match(target):
95
+ # target is a task id
96
+ task_id = target
97
+ else:
98
+ # target is an index path
99
+ task_id = find_task_id(target)
100
+
101
+ try:
102
+ # we have a decision task, add all tasks from task-graph.json
103
+ result = get_artifact(task_id, "public/task-graph.json").values()
104
+ task_defs.extend(result)
105
+ except HTTPError as e:
106
+ if e.response.status_code != 404:
107
+ raise
108
+
109
+ # we have a regular task, just yield its definition and move on
110
+ task_defs.append(get_task_definition(target))
111
+
112
+ for task_def in task_defs:
113
+ attributes = task_def.get("attributes", {})
114
+
115
+ # filter out some unsupported / undesired cases implicitly
116
+ if task_def["task"]["provisionerId"] == "releng-hardware":
117
+ continue
118
+
119
+ if (
120
+ task_def["task"]["payload"]
121
+ .get("features", {})
122
+ .get("runAsAdministrator")
123
+ ):
124
+ continue
125
+
126
+ # filter out tasks that don't satisfy include-attrs
127
+ if not attrmatch(attributes, **config.get("include-attrs", {})):
128
+ continue
129
+
130
+ # filter out tasks that satisfy exclude-attrs
131
+ if exclude_attrs := config.get("exclude-attrs"):
132
+ excludes = {
133
+ key: lambda attr: any([attr.startswith(v) for v in values])
134
+ for key, values in exclude_attrs.items()
135
+ }
136
+ if attrmatch(attributes, **excludes):
137
+ continue
138
+
139
+ task_def["name-prefix"] = task["name"]
140
+ yield task_def
141
+
142
+
143
+ def _rewrite_datestamps(task_def):
144
+ """Rewrite absolute datestamps from a concrete task definition into
145
+ relative ones that can then be used to schedule a new task."""
146
+ # Arguably, we should try to figure out what these values should be from
147
+ # the repo that created them originally. In practice it probably doesn't
148
+ # matter.
149
+ task_def["created"] = {"relative-datestamp": "0 seconds"}
150
+ task_def["deadline"] = {"relative-datestamp": "1 day"}
151
+ task_def["expires"] = {"relative-datestamp": "1 month"}
152
+
153
+ if artifacts := task_def.get("payload", {}).get("artifacts"):
154
+ artifacts = artifacts.values() if isinstance(artifacts, dict) else artifacts
155
+ for artifact in artifacts:
156
+ if "expires" in artifact:
157
+ artifact["expires"] = {"relative-datestamp": "1 month"}
158
+
159
+
160
+ def _remove_revisions(task_def):
161
+ """Rewrite revisions in task payloads to ensure that tasks do not refer to
162
+ out of date revisions."""
163
+ to_remove = set()
164
+ for k in task_def.get("payload", {}).get("env", {}):
165
+ if k.endswith("_REV"):
166
+ to_remove.add(k)
167
+
168
+ for k in to_remove:
169
+ del task_def["payload"]["env"][k]
170
+
171
+
172
+ @transforms.add
173
+ def rewrite_task(config, task_defs):
174
+ assert "TASK_ID" in os.environ
175
+
176
+ trust_domain = config.graph_config["trust-domain"]
177
+ level = config.params["level"]
178
+
179
+ # Replace strings like `gecko-level-3` with the active trust domain and
180
+ # level.
181
+ pattern = re.compile(r"[a-z]+-level-[1-3]")
182
+ repl = f"{trust_domain}-level-{level}"
183
+
184
+ for task_def in task_defs:
185
+ task = task_def["task"]
186
+
187
+ task.update(
188
+ {
189
+ "schedulerId": repl,
190
+ "taskGroupId": os.environ["TASK_ID"],
191
+ "priority": "low",
192
+ "routes": ["checks"],
193
+ }
194
+ )
195
+
196
+ # Remove treeherder config
197
+ if "treeherder" in task["extra"]:
198
+ del task["extra"]["treeherder"]
199
+
200
+ cache = task["payload"].get("cache", {})
201
+ for name, value in cache.copy().items():
202
+ del cache[name]
203
+ name = pattern.sub(repl, name)
204
+ cache[name] = value
205
+
206
+ for mount in task["payload"].get("mounts", []):
207
+ if "cacheName" in mount:
208
+ mount["cacheName"] = pattern.sub(
209
+ repl,
210
+ mount["cacheName"],
211
+ )
212
+
213
+ for i, scope in enumerate(task.get("scopes", [])):
214
+ task["scopes"][i] = pattern.sub(repl, scope)
215
+
216
+ # Drop down to level 1 to match the current context.
217
+ for key in ("taskQueueId", "provisionerId", "worker-type"):
218
+ if key in task:
219
+ task_def[key] = task[key].replace("3", level)
220
+
221
+ # All datestamps come in as absolute ones, many of which
222
+ # will be in the past. We need to rewrite these to relative
223
+ # ones to make the task reschedulable.
224
+ _rewrite_datestamps(task)
225
+
226
+ # We also need to remove absolute revisions from payloads
227
+ # to avoid issues with revisions not matching the refs
228
+ # that are given.
229
+ _remove_revisions(task)
230
+
231
+ name_prefix = task_def.pop("name-prefix")
232
+ task["metadata"]["name"] = f"{name_prefix}-{task['metadata']['name']}"
233
+ taskdesc = {
234
+ "label": task["metadata"]["name"],
235
+ "dependencies": {},
236
+ "description": task["metadata"]["description"],
237
+ "task": task,
238
+ "attributes": {"replicate": name_prefix},
239
+ }
240
+
241
+ yield taskdesc
@@ -26,7 +26,7 @@ def set_taskcluster_url(session_mocker):
26
26
 
27
27
  @pytest.fixture
28
28
  def responses():
29
- with RequestsMock() as rsps:
29
+ with RequestsMock(assert_all_requests_are_fired=True) as rsps:
30
30
  yield rsps
31
31
 
32
32
 
@@ -0,0 +1,275 @@
1
+ from itertools import count
2
+ from pprint import pprint
3
+ from unittest.mock import Mock
4
+
5
+ import pytest
6
+ from requests import HTTPError
7
+ from taskgraph.util.templates import merge
8
+
9
+ from mozilla_taskgraph.transforms.replicate import transforms as replicate_transforms
10
+
11
+ TC_ROOT_URL = "https://tc-tests.example.com"
12
+
13
+
14
+ def get_target_defs(*task_defs):
15
+ default = {
16
+ "task": {
17
+ "extra": {
18
+ "treeherder": "1",
19
+ },
20
+ "metadata": {"name": "task-b", "description": "description"},
21
+ "payload": {
22
+ "artifacts": {
23
+ "foo": {
24
+ "expires": "some datestamp",
25
+ },
26
+ },
27
+ "cache": {
28
+ "foo-level-3": "1",
29
+ },
30
+ "env": {
31
+ "SHOULD_NOT_BE_REMOVED": "1",
32
+ "SHOULD_BE_REMOVED_REV": "1",
33
+ },
34
+ "mounts": [
35
+ {
36
+ "cacheName": "cache-foo-level-3-name",
37
+ }
38
+ ],
39
+ },
40
+ "provisionerId": "foo",
41
+ "scopes": [
42
+ "test:foo-level-3:scope",
43
+ ],
44
+ }
45
+ }
46
+
47
+ task_defs = task_defs or [{}]
48
+ return [merge(default, task_def) for task_def in task_defs]
49
+
50
+
51
+ def get_expected(prefix, *task_defs):
52
+ expected = []
53
+ for task_def in task_defs:
54
+ expected_task = merge(
55
+ task_def,
56
+ {
57
+ "attributes": {
58
+ "replicate": prefix,
59
+ },
60
+ "dependencies": {},
61
+ "description": "description",
62
+ "label": f"{prefix}-{task_def['task']['metadata']['name']}",
63
+ "task": {
64
+ "created": {"relative-datestamp": "0 seconds"},
65
+ "deadline": {"relative-datestamp": "1 day"},
66
+ "expires": {"relative-datestamp": "1 month"},
67
+ "metadata": {
68
+ "name": f"{prefix}-{task_def['task']['metadata']['name']}",
69
+ },
70
+ "payload": {
71
+ "artifacts": {
72
+ "foo": {
73
+ "expires": {
74
+ "relative-datestamp": "1 month",
75
+ }
76
+ }
77
+ },
78
+ "cache": {
79
+ "test-level-1": "1",
80
+ },
81
+ "mounts": [{"cacheName": "cache-test-level-1-name"}],
82
+ },
83
+ "priority": "low",
84
+ "routes": [
85
+ "checks",
86
+ ],
87
+ "schedulerId": "test-level-1",
88
+ "scopes": [
89
+ "test:test-level-1:scope",
90
+ ],
91
+ "taskGroupId": "abc",
92
+ },
93
+ },
94
+ )
95
+ del expected_task["task"]["extra"]["treeherder"]
96
+ del expected_task["task"]["payload"]["cache"]["foo-level-3"]
97
+ del expected_task["task"]["payload"]["env"]["SHOULD_BE_REMOVED_REV"]
98
+ del expected_task["task"]["payload"]["mounts"][0]
99
+ del expected_task["task"]["scopes"][0]
100
+ expected.append(expected_task)
101
+
102
+ return expected
103
+
104
+
105
+ @pytest.fixture
106
+ def run_replicate(monkeypatch, run_transform):
107
+ task_id = "abc"
108
+ monkeypatch.setenv("TASK_ID", task_id)
109
+
110
+ def inner(task):
111
+ result = run_transform(replicate_transforms, task)
112
+ pprint(result, indent=2)
113
+ return result
114
+
115
+ return inner
116
+
117
+
118
+ def test_missing_config(run_replicate):
119
+ task = {}
120
+ with pytest.raises(Exception):
121
+ run_replicate(task)
122
+
123
+ task["replicate"] = {}
124
+ with pytest.raises(Exception):
125
+ run_replicate(task)
126
+
127
+ task["replicate"]["target"] = []
128
+ assert run_replicate(task) == []
129
+
130
+
131
+ def test_requests_error(responses, run_replicate):
132
+ task_id = "fwp41cUkRmara7CD6l2U3A"
133
+ task = {
134
+ "name": "foo",
135
+ "replicate": {
136
+ "target": [
137
+ task_id,
138
+ ]
139
+ },
140
+ }
141
+ responses.get(
142
+ f"{TC_ROOT_URL}/api/queue/v1/task/{task_id}/artifacts/public/task-graph.json",
143
+ body=HTTPError("Artifact not found!", response=Mock(status_code=403)),
144
+ )
145
+
146
+ with pytest.raises(HTTPError):
147
+ run_replicate(task)
148
+
149
+
150
+ def test_task_id(responses, run_replicate):
151
+ task_id = "fwp41cUkRmara7CD6l2U3A"
152
+ prefix = "kind-a"
153
+ task = {
154
+ "name": prefix,
155
+ "replicate": {
156
+ "target": [
157
+ task_id,
158
+ ]
159
+ },
160
+ }
161
+ task_def = get_target_defs()[0]
162
+ expected = get_expected(prefix, task_def)[0]
163
+
164
+ responses.get(
165
+ f"{TC_ROOT_URL}/api/queue/v1/task/{task_id}/artifacts/public/task-graph.json",
166
+ body=HTTPError("Artifact not found!", response=Mock(status_code=404)),
167
+ )
168
+ responses.get(f"{TC_ROOT_URL}/api/queue/v1/task/{task_id}", json=task_def)
169
+
170
+ result = run_replicate(task)
171
+ assert len(result) == 1
172
+ assert result[0] == expected
173
+
174
+
175
+ def test_index_path(responses, run_replicate):
176
+ prefix = "kind-a"
177
+ task_id = "def"
178
+ index_path = "foo.bar"
179
+ task = {
180
+ "name": prefix,
181
+ "replicate": {"target": [index_path]},
182
+ }
183
+ task_def = get_target_defs()[0]
184
+ expected = get_expected(prefix, task_def)[0]
185
+
186
+ responses.get(
187
+ f"{TC_ROOT_URL}/api/index/v1/task/{index_path}", json={"taskId": task_id}
188
+ )
189
+ responses.get(
190
+ f"{TC_ROOT_URL}/api/queue/v1/task/{task_id}/artifacts/public/task-graph.json",
191
+ body=HTTPError("Artifact not found!", response=Mock(status_code=404)),
192
+ )
193
+ responses.get(f"{TC_ROOT_URL}/api/queue/v1/task/{index_path}", json=task_def)
194
+
195
+ result = run_replicate(task)
196
+ assert len(result) == 1
197
+ assert result[0] == expected
198
+
199
+
200
+ def test_decision_task(responses, run_replicate):
201
+ prefix = "kind-a"
202
+ task_id = "fwp41cUkRmara7CD6l2U3A"
203
+ task = {
204
+ "name": prefix,
205
+ "replicate": {
206
+ "target": [
207
+ task_id,
208
+ ]
209
+ },
210
+ }
211
+ task_defs = get_target_defs({}, {"task": {"metadata": {"name": "task-c"}}})
212
+ expected = get_expected(prefix, *task_defs)
213
+
214
+ counter = count()
215
+ responses.get(
216
+ f"{TC_ROOT_URL}/api/queue/v1/task/{task_id}/artifacts/public/task-graph.json",
217
+ json={next(counter): task_def for task_def in task_defs},
218
+ )
219
+ result = run_replicate(task)
220
+ assert result == expected
221
+
222
+
223
+ @pytest.mark.parametrize(
224
+ "target_def",
225
+ (
226
+ pytest.param(
227
+ {
228
+ "attributes": {"foo": "bar"},
229
+ "task": {"provisionerId": "releng-hardware"},
230
+ },
231
+ id="releng-hardware",
232
+ ),
233
+ pytest.param(
234
+ {
235
+ "attributes": {"foo": "bar"},
236
+ "task": {"payload": {"features": {"runAsAdministrator": True}}},
237
+ },
238
+ id="runAsAdministrator",
239
+ ),
240
+ pytest.param(
241
+ {}, # doesn't match 'include-attrs'
242
+ id="include-attrs",
243
+ ),
244
+ pytest.param(
245
+ {"attributes": {"foo": "bar", "baz": "1"}},
246
+ id="exclude-attrs",
247
+ ),
248
+ ),
249
+ )
250
+ def test_filtered_out(responses, run_replicate, target_def):
251
+ prefix = "kind-a"
252
+ task_id = "fwp41cUkRmara7CD6l2U3A"
253
+ task = {
254
+ "name": prefix,
255
+ "replicate": {
256
+ "target": [
257
+ task_id,
258
+ ],
259
+ "include-attrs": {
260
+ "foo": "bar",
261
+ },
262
+ "exclude-attrs": {
263
+ "baz": "1",
264
+ },
265
+ },
266
+ }
267
+ task_defs = get_target_defs(target_def)
268
+
269
+ counter = count()
270
+ responses.get(
271
+ f"{TC_ROOT_URL}/api/queue/v1/task/{task_id}/artifacts/public/task-graph.json",
272
+ json={next(counter): task_def for task_def in task_defs},
273
+ )
274
+ result = run_replicate(task)
275
+ assert len(result) == 0