scriptworker 63.3.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.
Files changed (30) hide show
  1. {scriptworker-63.3.0 → scriptworker-63.4.1}/HISTORY.rst +15 -0
  2. {scriptworker-63.3.0 → scriptworker-63.4.1}/PKG-INFO +2 -2
  3. {scriptworker-63.3.0 → scriptworker-63.4.1}/pyproject.toml +1 -1
  4. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/artifacts.py +34 -4
  5. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/cot/verify.py +19 -5
  6. {scriptworker-63.3.0 → scriptworker-63.4.1}/.gitignore +0 -0
  7. {scriptworker-63.3.0 → scriptworker-63.4.1}/CONTRIBUTING.rst +0 -0
  8. {scriptworker-63.3.0 → scriptworker-63.4.1}/LICENSE +0 -0
  9. {scriptworker-63.3.0 → scriptworker-63.4.1}/README.rst +0 -0
  10. {scriptworker-63.3.0 → scriptworker-63.4.1}/renovate.json +0 -0
  11. {scriptworker-63.3.0 → scriptworker-63.4.1}/scripts/gen_ed25519_key.py +0 -0
  12. {scriptworker-63.3.0 → scriptworker-63.4.1}/scriptworker.yaml.tmpl +0 -0
  13. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/__init__.py +0 -0
  14. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/client.py +0 -0
  15. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/config.py +0 -0
  16. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/constants.py +0 -0
  17. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/context.py +0 -0
  18. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/cot/__init__.py +0 -0
  19. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/cot/generate.py +0 -0
  20. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/data/cot_v1_schema.json +0 -0
  21. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/data/scriptworker_task_schema.json +0 -0
  22. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/ed25519.py +0 -0
  23. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/exceptions.py +0 -0
  24. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/github.py +0 -0
  25. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/log.py +0 -0
  26. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/task.py +0 -0
  27. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/task_process.py +0 -0
  28. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/utils.py +0 -0
  29. {scriptworker-63.3.0 → scriptworker-63.4.1}/src/scriptworker/worker.py +0 -0
  30. {scriptworker-63.3.0 → scriptworker-63.4.1}/tox.ini +0 -0
@@ -4,6 +4,21 @@ 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
+
7
22
  63.3.0 - 2026-08-05
8
23
  -------------------
9
24
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: scriptworker
3
- Version: 63.3.0
3
+ Version: 63.4.1
4
4
  Summary: Taskcluster scriptworker
5
5
  Author-email: Mozilla Release Engineering <releng@mozilla.com>
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "scriptworker"
3
- version = "63.3.0"
3
+ version = "63.4.1"
4
4
  description = "Taskcluster scriptworker"
5
5
  authors = [
6
6
  { name = "Mozilla Release Engineering", email = "releng@mozilla.com" },
@@ -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 add_enumerable_item_to_dict, download_file, get_loggable_url, raise_future_exceptions, retry_async, semaphore_wrapper
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 _craft_artifact_put_headers(content_type, encoding=None):
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
@@ -1952,12 +1952,26 @@ def get_source_url(obj):
1952
1952
  log.debug("Getting source url for {} {}...".format(obj.name, obj.task_id))
1953
1953
  repo = get_repo(obj.task, source_env_prefix=source_env_prefix)
1954
1954
  source = task["metadata"]["source"]
1955
- if repo and not verify_repo_matches_url(repo, source):
1956
- raise CoTError(
1957
- "{name} {task_id}: {source_env_prefix} {repo} doesn't match source {source}!".format(
1958
- name=obj.name, task_id=obj.task_id, source_env_prefix=source_env_prefix, repo=repo, source=source
1955
+ cot_product = obj.context.config["cot_product"]
1956
+ match_repo = repo and verify_repo_matches_url(repo, source)
1957
+ if cot_product != "enterprise":
1958
+ if repo and not match_repo:
1959
+ raise CoTError(
1960
+ "{name} {task_id}: {source_env_prefix} {repo} doesn't match source {source}!".format(
1961
+ name=obj.name, task_id=obj.task_id, source_env_prefix=source_env_prefix, repo=repo, source=source
1962
+ )
1959
1963
  )
1960
- )
1964
+ else:
1965
+ comm_repo = get_repo(obj.task, source_env_prefix="COMM", repo_type="head")
1966
+ match_comm_repo = comm_repo and verify_repo_matches_url(comm_repo, source)
1967
+
1968
+ if not match_repo and not match_comm_repo:
1969
+ raise CoTError(
1970
+ "{name} {task_id}: {source_env_prefix} {repo} or {comm_repo} doesn't match source {source}!".format(
1971
+ name=obj.name, task_id=obj.task_id, source_env_prefix=source_env_prefix, repo=repo, comm_repo=comm_repo, source=source
1972
+ )
1973
+ )
1974
+
1961
1975
  log.info("{} {}: found {}".format(obj.name, obj.task_id, source))
1962
1976
  return source
1963
1977
 
File without changes
File without changes
File without changes
File without changes