scriptworker 63.2.0__tar.gz → 63.4.1__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.
- {scriptworker-63.2.0 → scriptworker-63.4.1}/HISTORY.rst +37 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/PKG-INFO +2 -2
- {scriptworker-63.2.0 → scriptworker-63.4.1}/pyproject.toml +1 -1
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/artifacts.py +34 -4
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/config.py +13 -2
- scriptworker-63.4.1/src/scriptworker/constants.py +840 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/context.py +11 -3
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/cot/verify.py +19 -5
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/utils.py +65 -14
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/worker.py +4 -2
- scriptworker-63.2.0/src/scriptworker/constants.py +0 -822
- {scriptworker-63.2.0 → scriptworker-63.4.1}/.gitignore +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/CONTRIBUTING.rst +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/LICENSE +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/README.rst +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/renovate.json +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/scripts/gen_ed25519_key.py +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/scriptworker.yaml.tmpl +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/__init__.py +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/client.py +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/cot/__init__.py +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/cot/generate.py +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/data/cot_v1_schema.json +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/data/scriptworker_task_schema.json +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/ed25519.py +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/exceptions.py +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/github.py +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/log.py +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/task.py +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/src/scriptworker/task_process.py +0 -0
- {scriptworker-63.2.0 → scriptworker-63.4.1}/tox.ini +0 -0
|
@@ -4,6 +4,43 @@ Change Log
|
|
|
4
4
|
All notable changes to this project will be documented in this file.
|
|
5
5
|
This project adheres to `Semantic Versioning <http://semver.org/>`__.
|
|
6
6
|
|
|
7
|
+
63.4.1 - 2026-08-11
|
|
8
|
+
-------------------
|
|
9
|
+
|
|
10
|
+
Fixed
|
|
11
|
+
~~~~~
|
|
12
|
+
|
|
13
|
+
- Allow enterprise tasks to have comm source repositories
|
|
14
|
+
|
|
15
|
+
63.4.0 - 2026-08-11
|
|
16
|
+
-------------------
|
|
17
|
+
|
|
18
|
+
Added
|
|
19
|
+
~~~~~
|
|
20
|
+
- set Content-MD5 header to avoid silent data corruption uploading artifacts (#815)
|
|
21
|
+
|
|
22
|
+
63.3.0 - 2026-08-05
|
|
23
|
+
-------------------
|
|
24
|
+
|
|
25
|
+
Added
|
|
26
|
+
~~~~~
|
|
27
|
+
|
|
28
|
+
- `cot_product` is now optional. When unset, `by-cot-product` mappings resolve to
|
|
29
|
+
`None`, and `check_config` requires `verify_chain_of_trust` to be off (#810)
|
|
30
|
+
- `download_file` accepts an optional `expected_content_type` kwarg that rejects
|
|
31
|
+
`text/html` responses when another type was requested (#791)
|
|
32
|
+
|
|
33
|
+
Fixed
|
|
34
|
+
~~~~~
|
|
35
|
+
|
|
36
|
+
- Event loop creation in a fresh process, which raised a `RuntimeError` on
|
|
37
|
+
Python 3.14 since `asyncio.get_event_loop()` no longer creates a loop (#811)
|
|
38
|
+
- JSON/YAML load failures now include the file path and, for downloaded
|
|
39
|
+
artifacts, the source URL in the error message (#791)
|
|
40
|
+
- `load_json_or_yaml_from_url` retries the download when the cached file fails
|
|
41
|
+
to parse, turning transient artifact-server glitches into self-healing
|
|
42
|
+
retries instead of hard failures (#791)
|
|
43
|
+
|
|
7
44
|
63.2.0 - 2026-06-18
|
|
8
45
|
-------------------
|
|
9
46
|
|
|
@@ -6,6 +6,7 @@ in S3.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import asyncio
|
|
9
|
+
import base64
|
|
9
10
|
import fnmatch
|
|
10
11
|
import gzip
|
|
11
12
|
import logging
|
|
@@ -21,7 +22,15 @@ from taskcluster.exceptions import TaskclusterFailure
|
|
|
21
22
|
from scriptworker.client import validate_artifact_url
|
|
22
23
|
from scriptworker.exceptions import DownloadError, ScriptWorkerRetryException, ScriptWorkerTaskException
|
|
23
24
|
from scriptworker.task import get_decision_task_id, get_run_id, get_task_id
|
|
24
|
-
from scriptworker.utils import
|
|
25
|
+
from scriptworker.utils import (
|
|
26
|
+
add_enumerable_item_to_dict,
|
|
27
|
+
download_file,
|
|
28
|
+
get_hash,
|
|
29
|
+
get_loggable_url,
|
|
30
|
+
raise_future_exceptions,
|
|
31
|
+
retry_async,
|
|
32
|
+
semaphore_wrapper,
|
|
33
|
+
)
|
|
25
34
|
|
|
26
35
|
log = logging.getLogger(__name__)
|
|
27
36
|
|
|
@@ -163,6 +172,8 @@ async def create_artifact(context, path, target_path, content_type, content_enco
|
|
|
163
172
|
payload = {"storageType": storage_type, "expires": expires or get_expiration_arrow(context).isoformat(), "contentType": content_type}
|
|
164
173
|
args = [get_task_id(context.claim_task), get_run_id(context.claim_task), target_path, payload]
|
|
165
174
|
|
|
175
|
+
content_md5 = await asyncio.to_thread(get_content_md5, path)
|
|
176
|
+
|
|
166
177
|
tc_response = await context.temp_queue.createArtifact(*args)
|
|
167
178
|
skip_auto_headers = [aiohttp.hdrs.CONTENT_TYPE]
|
|
168
179
|
loggable_url = get_loggable_url(tc_response["putUrl"])
|
|
@@ -172,7 +183,7 @@ async def create_artifact(context, path, target_path, content_type, content_enco
|
|
|
172
183
|
async with context.session.put(
|
|
173
184
|
tc_response["putUrl"],
|
|
174
185
|
data=fh,
|
|
175
|
-
headers=_craft_artifact_put_headers(content_type, content_encoding),
|
|
186
|
+
headers=_craft_artifact_put_headers(content_type, content_md5, content_encoding),
|
|
176
187
|
skip_auto_headers=skip_auto_headers,
|
|
177
188
|
compress=False,
|
|
178
189
|
) as resp:
|
|
@@ -180,6 +191,12 @@ async def create_artifact(context, path, target_path, content_type, content_enco
|
|
|
180
191
|
response_text = await resp.text()
|
|
181
192
|
log.info(response_text)
|
|
182
193
|
if resp.status not in (200, 204):
|
|
194
|
+
if content_md5 is not None and "BadDigest" in (response_text or ""):
|
|
195
|
+
log.error(
|
|
196
|
+
"{} was corrupted in transit: the storage backend rejected the body as not matching Content-MD5 {}. Retrying.".format(
|
|
197
|
+
target_path, content_md5
|
|
198
|
+
)
|
|
199
|
+
)
|
|
183
200
|
raise ScriptWorkerRetryException("Bad status {}".format(resp.status))
|
|
184
201
|
|
|
185
202
|
|
|
@@ -206,9 +223,22 @@ async def create_link_artifact(context, target_path, link_to, content_type, expi
|
|
|
206
223
|
await context.temp_queue.createArtifact(*args)
|
|
207
224
|
|
|
208
225
|
|
|
209
|
-
def
|
|
226
|
+
def get_content_md5(path):
|
|
227
|
+
"""Get the base64-encoded md5 digest of a file, formatted for ``Content-MD5``.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
path (str): the path to the file to digest.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
str: the base64-encoded md5 digest.
|
|
234
|
+
|
|
235
|
+
"""
|
|
236
|
+
return base64.b64encode(bytes.fromhex(get_hash(path, hash_alg="md5"))).decode("ascii")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _craft_artifact_put_headers(content_type, content_md5, encoding=None):
|
|
210
240
|
log.debug("{} {}".format(content_type, encoding))
|
|
211
|
-
headers = {aiohttp.hdrs.CONTENT_TYPE: content_type}
|
|
241
|
+
headers = {aiohttp.hdrs.CONTENT_TYPE: content_type, aiohttp.hdrs.CONTENT_MD5: content_md5}
|
|
212
242
|
|
|
213
243
|
if encoding is not None:
|
|
214
244
|
headers[aiohttp.hdrs.CONTENT_ENCODING] = encoding
|
|
@@ -125,11 +125,17 @@ def check_config(config, path):
|
|
|
125
125
|
if key not in DEFAULT_CONFIG:
|
|
126
126
|
messages.append("Unknown key {} in {}!".format(key, path))
|
|
127
127
|
continue
|
|
128
|
+
is_by_cot_product = isinstance(DEFAULT_CONFIG[key], Mapping) and "by-cot-product" in DEFAULT_CONFIG[key]
|
|
128
129
|
if value is None:
|
|
129
|
-
|
|
130
|
+
if key != "cot_product" and not is_by_cot_product:
|
|
131
|
+
messages.append(_VALUE_UNDEFINED_MESSAGE.format(path=path, key=key))
|
|
130
132
|
else:
|
|
131
133
|
value_type = type(value)
|
|
132
|
-
if
|
|
134
|
+
if key == "cot_product":
|
|
135
|
+
# `cot_product`'s own default is `None`, so its type can't be
|
|
136
|
+
# inferred from `DEFAULT_CONFIG` the way other keys' can.
|
|
137
|
+
default_type = str
|
|
138
|
+
elif is_by_cot_product:
|
|
133
139
|
default_type = type(DEFAULT_CONFIG[key]["by-cot-product"][config["cot_product"]])
|
|
134
140
|
else:
|
|
135
141
|
default_type = type(DEFAULT_CONFIG[key])
|
|
@@ -139,6 +145,8 @@ def check_config(config, path):
|
|
|
139
145
|
messages.append(_VALUE_UNDEFINED_MESSAGE.format(path=path, key=key))
|
|
140
146
|
if key in ("provisioner_id", "worker_group", "worker_type", "worker_id") and not _is_id_valid(value):
|
|
141
147
|
messages.append('{} doesn\'t match "{}" (required by Taskcluster)'.format(key, _GENERIC_ID_REGEX.pattern))
|
|
148
|
+
if config.get("cot_product") is None and config.get("verify_chain_of_trust"):
|
|
149
|
+
messages.append("{} verify_chain_of_trust can't be True when cot_product is not set!".format(path))
|
|
142
150
|
return messages
|
|
143
151
|
|
|
144
152
|
|
|
@@ -161,6 +169,9 @@ def apply_product_config(config):
|
|
|
161
169
|
|
|
162
170
|
for key in config:
|
|
163
171
|
if isinstance(config[key], Mapping) and "by-cot-product" in config[key]:
|
|
172
|
+
if cot_product is None:
|
|
173
|
+
config[key] = None
|
|
174
|
+
continue
|
|
164
175
|
try:
|
|
165
176
|
config[key] = config[key]["by-cot-product"][cot_product]
|
|
166
177
|
except KeyError:
|