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.
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.pre-commit-config.yaml +2 -2
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/CHANGELOG.md +6 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/PKG-INFO +1 -1
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/pyproject.toml +3 -4
- mozilla_taskgraph-3.4.0/src/mozilla_taskgraph/transforms/replicate.py +241 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/conftest.py +1 -1
- mozilla_taskgraph-3.4.0/test/transforms/test_replicate.py +275 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/uv.lock +414 -414
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.codespell-ignore-words.txt +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.github/CODEOWNERS +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.github/workflows/pre-commit-autoupdate.yml +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.github/workflows/pre-commit.yml +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.github/workflows/pypi-publish.yml +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.gitignore +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.readthedocs.yml +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.taskcluster.yml +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/.yamllint +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/CODE_OF_CONDUCT.md +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/CONTRIBUTING.md +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/LICENSE +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/Makefile +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/README.md +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/docs/actions/index.rst +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/docs/actions/release-promotion.rst +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/docs/conf.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/docs/index.rst +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/docs/transforms/index.rst +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/docs/transforms/scriptworker/index.rst +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/docs/transforms/scriptworker/release_artifacts.rst +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/docs/transforms/scriptworker/ship-it.rst +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/__init__.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/actions/__init__.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/actions/release_promotion.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/config.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/transforms/__init__.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/transforms/scriptworker/__init__.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/transforms/scriptworker/release_artifacts.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/transforms/scriptworker/shipit/__init__.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/transforms/scriptworker/shipit/mark_as_shipped.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/util/signed_artifacts.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/version.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/src/mozilla_taskgraph/worker_types.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/config.yml +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/docker/fetch/Dockerfile +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/docker/fetch/REGISTRY +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/docker/python/Dockerfile +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/kinds/check/kind.yml +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/kinds/codecov/kind.yml +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/kinds/docker-image/kind.yml +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/kinds/fetch/kind.yml +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/kinds/release-promotion-dummy/kind.yml +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/kinds/test/kind.yml +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/mt_taskgraph/target_tasks.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/mt_taskgraph/transforms/test.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/taskcluster/scripts/codecov-upload.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/__init__.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/actions/__init__.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/actions/test_release_promotion.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/data/taskcluster/config.yml +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/data/testver.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/test_register.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/test_version.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/test_worker_types.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/transforms/test_scriptworker_release_artifacts.py +0 -0
- {mozilla_taskgraph-3.3.2 → mozilla_taskgraph-3.4.0}/test/transforms/test_scriptworker_shipit.py +0 -0
- {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.
|
|
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.
|
|
20
|
+
rev: 'v0.12.1'
|
|
21
21
|
hooks:
|
|
22
22
|
- id: ruff
|
|
23
23
|
args: [--fix, --exit-non-zero-on-fix]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mozilla-taskgraph
|
|
3
|
-
Version: 3.
|
|
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
|
+
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
|
|
@@ -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
|