scriptworker 62.7.0__tar.gz → 63.0.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-62.7.0 → scriptworker-63.0.1}/HISTORY.rst +25 -0
  2. {scriptworker-62.7.0 → scriptworker-63.0.1}/PKG-INFO +1 -1
  3. {scriptworker-62.7.0 → scriptworker-63.0.1}/pyproject.toml +1 -1
  4. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/cot/verify.py +32 -22
  5. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/utils.py +16 -6
  6. {scriptworker-62.7.0 → scriptworker-63.0.1}/tox.ini +1 -0
  7. {scriptworker-62.7.0 → scriptworker-63.0.1}/.gitignore +0 -0
  8. {scriptworker-62.7.0 → scriptworker-63.0.1}/CONTRIBUTING.rst +0 -0
  9. {scriptworker-62.7.0 → scriptworker-63.0.1}/LICENSE +0 -0
  10. {scriptworker-62.7.0 → scriptworker-63.0.1}/README.rst +0 -0
  11. {scriptworker-62.7.0 → scriptworker-63.0.1}/renovate.json +0 -0
  12. {scriptworker-62.7.0 → scriptworker-63.0.1}/scripts/gen_ed25519_key.py +0 -0
  13. {scriptworker-62.7.0 → scriptworker-63.0.1}/scriptworker.yaml.tmpl +0 -0
  14. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/__init__.py +0 -0
  15. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/artifacts.py +0 -0
  16. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/client.py +0 -0
  17. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/config.py +0 -0
  18. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/constants.py +0 -0
  19. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/context.py +0 -0
  20. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/cot/__init__.py +0 -0
  21. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/cot/generate.py +0 -0
  22. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/data/cot_v1_schema.json +0 -0
  23. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/data/scriptworker_task_schema.json +0 -0
  24. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/ed25519.py +0 -0
  25. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/exceptions.py +0 -0
  26. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/github.py +0 -0
  27. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/log.py +0 -0
  28. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/task.py +0 -0
  29. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/task_process.py +0 -0
  30. {scriptworker-62.7.0 → scriptworker-63.0.1}/src/scriptworker/worker.py +0 -0
@@ -4,6 +4,31 @@ 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.0.1 - 2026-04-27
8
+
9
+ Fixed
10
+ ~~~~~
11
+
12
+ - File corruption caused by parallel downloads (#792)
13
+
14
+ 63.0.0 - 2026-04-09
15
+ -------------------
16
+
17
+ Changed
18
+ ~~~~~~~
19
+
20
+ - Parallelized several steps of Chain of Trust verification, significantly
21
+ improving end-to-end times. As a result, `scriptworker.cot.verify.build_link`
22
+ has been renamed to `scriptworker.cot.verify.add_link` and no longer recurses
23
+ into dependencies . One should call `build_task_dependencies` after adding
24
+ the link to walk the graph.
25
+
26
+ Fixed
27
+ ~~~~~
28
+
29
+ - The root task isn't verified twice anymore when using `check_task=True` in
30
+ `verify_chain_of_trust` on non decision tasks.
31
+
7
32
  62.7.0 - 2026-03-18
8
33
  -------------------
9
34
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scriptworker
3
- Version: 62.7.0
3
+ Version: 63.0.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 = "62.7.0"
3
+ version = "63.0.1"
4
4
  description = "Taskcluster scriptworker"
5
5
  authors = [
6
6
  { name = "Mozilla Release Engineering", email = "releng@mozilla.com" },
@@ -222,7 +222,7 @@ class ChainOfTrust(object):
222
222
  list: of all ``LinkOfTrust``s to verify.
223
223
 
224
224
  """
225
- if self.is_decision() and self.get_link(self.task_id):
225
+ if self.task_id in self.dependent_task_ids():
226
226
  return self.links
227
227
  return [self] + self.links
228
228
 
@@ -619,8 +619,8 @@ def _sort_dependencies_by_name_then_task_id(dependencies):
619
619
 
620
620
 
621
621
  # build_task_dependencies {{{1
622
- async def build_link(chain, task_name, task_id):
623
- """Build a LinkOfTrust and add it to the chain.
622
+ async def add_link(chain, task_name, task_id):
623
+ """Fetch a task definition and add it as a LinkOfTrust to the chain.
624
624
 
625
625
  Args:
626
626
  chain (ChainOfTrust): the chain of trust to add to.
@@ -633,17 +633,14 @@ async def build_link(chain, task_name, task_id):
633
633
  """
634
634
  link = LinkOfTrust(chain.context, task_name, task_id)
635
635
  json_path = link.get_artifact_full_path("task.json")
636
- task_defn = await retry_get_task_definition(chain.context.queue, task_id, exception=CoTError)
637
- link.task = task_defn
636
+ link.task = await retry_get_task_definition(chain.context.queue, task_id, exception=CoTError)
638
637
  chain.links.append(link)
639
- # write task json to disk
640
638
  makedirs(os.path.dirname(json_path))
641
639
  with open(json_path, "w") as fh:
642
- fh.write(format_json(task_defn))
643
- await build_task_dependencies(chain, task_defn, task_name, task_id)
640
+ fh.write(format_json(link.task))
644
641
 
645
642
 
646
- async def build_task_dependencies(chain, task, name, my_task_id):
643
+ async def build_task_dependencies(chain, task, name, my_task_id, seen=None):
647
644
  """Recursively build the task dependencies of a task.
648
645
 
649
646
  Args:
@@ -651,6 +648,7 @@ async def build_task_dependencies(chain, task, name, my_task_id):
651
648
  task (dict): the task definition to operate on.
652
649
  name (str): the name of the task to operate on.
653
650
  my_task_id (str): the taskId of the task to operate on.
651
+ seen (set): shared set of already-seen task IDs to avoid duplicates
654
652
 
655
653
  Raises:
656
654
  CoTError: on failure.
@@ -661,9 +659,20 @@ async def build_task_dependencies(chain, task, name, my_task_id):
661
659
  raise CoTError("Too deep recursion!\n{}".format(name))
662
660
  sorted_dependencies = find_sorted_task_dependencies(task, name, my_task_id)
663
661
 
662
+ if seen is None:
663
+ seen = set(chain.dependent_task_ids())
664
+ new_deps = []
664
665
  for task_name, task_id in sorted_dependencies:
665
- if task_id not in chain.dependent_task_ids():
666
- await build_link(chain, task_name, task_id)
666
+ if task_id not in seen:
667
+ seen.add(task_id)
668
+ new_deps.append((task_name, task_id))
669
+
670
+ if not new_deps:
671
+ return
672
+
673
+ await asyncio.gather(*[add_link(chain, task_name, task_id) for task_name, task_id in new_deps])
674
+
675
+ await asyncio.gather(*[build_task_dependencies(chain, chain.get_link(task_id).task, task_name, task_id, seen) for task_name, task_id in new_deps])
667
676
 
668
677
 
669
678
  # download_cot {{{1
@@ -1565,8 +1574,10 @@ async def get_jsone_context_and_template(chain, parent_link, decision_link, task
1565
1574
  if tasks_for in ("action", "pr-action"):
1566
1575
  jsone_context, tmpl = await get_action_context_and_template(chain, parent_link, decision_link, tasks_for)
1567
1576
  else:
1568
- tmpl = await get_in_tree_template(decision_link)
1569
- jsone_context = await populate_jsone_context(chain, parent_link, decision_link, tasks_for)
1577
+ tmpl, jsone_context = await asyncio.gather(
1578
+ get_in_tree_template(decision_link),
1579
+ populate_jsone_context(chain, parent_link, decision_link, tasks_for),
1580
+ )
1570
1581
  return jsone_context, tmpl
1571
1582
 
1572
1583
 
@@ -1803,14 +1814,14 @@ async def verify_task_types(chain):
1803
1814
  """
1804
1815
  valid_task_types = get_valid_task_types()
1805
1816
  task_count = {}
1817
+ tasks = []
1806
1818
  for obj in chain.get_all_links_in_chain():
1807
1819
  task_type = obj.task_type
1808
1820
  log.info("Verifying {} {} as a {} task...".format(obj.name, obj.task_id, task_type))
1809
1821
  task_count.setdefault(task_type, 0)
1810
1822
  task_count[task_type] += 1
1811
- # Run tests synchronously for now. We can parallelize if efficiency
1812
- # is more important than a single simple logfile.
1813
- await valid_task_types[task_type](chain, obj)
1823
+ tasks.append(valid_task_types[task_type](chain, obj))
1824
+ await asyncio.gather(*tasks)
1814
1825
  return task_count
1815
1826
 
1816
1827
 
@@ -1880,12 +1891,12 @@ async def verify_worker_impls(chain):
1880
1891
 
1881
1892
  """
1882
1893
  valid_worker_impls = get_valid_worker_impls()
1894
+ tasks = []
1883
1895
  for obj in chain.get_all_links_in_chain():
1884
1896
  worker_impl = obj.worker_impl
1885
1897
  log.info("Verifying {} {} as a {} task...".format(obj.name, obj.task_id, worker_impl))
1886
- # Run tests synchronously for now. We can parallelize if efficiency
1887
- # is more important than a single simple logfile.
1888
- await valid_worker_impls[worker_impl](chain, obj)
1898
+ tasks.append(valid_worker_impls[worker_impl](chain, obj))
1899
+ await asyncio.gather(*tasks)
1889
1900
 
1890
1901
 
1891
1902
  # get_source_url {{{1
@@ -2042,9 +2053,8 @@ async def verify_chain_of_trust(chain, *, check_task=False):
2042
2053
  try:
2043
2054
  # build LinkOfTrust objects
2044
2055
  if check_task:
2045
- await build_link(chain, chain.name, chain.task_id)
2046
- else:
2047
- await build_task_dependencies(chain, chain.task, chain.name, chain.task_id)
2056
+ await add_link(chain, chain.name, chain.task_id)
2057
+ await build_task_dependencies(chain, chain.task, chain.name, chain.task_id)
2048
2058
  # download the signed chain of trust artifacts
2049
2059
  await download_cot(chain)
2050
2060
  # verify the signatures and populate the ``link.cot``s
@@ -16,6 +16,7 @@ import random
16
16
  import re
17
17
  import shutil
18
18
  import time
19
+ import uuid
19
20
  from copy import deepcopy
20
21
  from typing import IO, TYPE_CHECKING, Any, Awaitable, Callable, Dict, Match, Optional, Sequence, Tuple, Type, Union, cast, overload # noqa
21
22
  from urllib.parse import unquote, urlparse
@@ -680,12 +681,21 @@ async def download_file(context, url, abs_filename, session=None, chunk_size=128
680
681
  await _log_download_error(resp, "Failed to download %(url)s: %(status)s; body=%(body)s")
681
682
  raise DownloadError("{} status {} is not 200!".format(loggable_url, resp.status))
682
683
  makedirs(parent_dir)
683
- with open(abs_filename, "wb") as fd:
684
- while True:
685
- chunk = await resp.content.read(chunk_size)
686
- if not chunk:
687
- break
688
- fd.write(chunk)
684
+ tmp_filename = "{}.{}.part".format(abs_filename, uuid.uuid4().hex)
685
+ try:
686
+ with open(tmp_filename, "wb") as fd:
687
+ while True:
688
+ chunk = await resp.content.read(chunk_size)
689
+ if not chunk:
690
+ break
691
+ fd.write(chunk)
692
+ os.replace(tmp_filename, abs_filename)
693
+ except Exception:
694
+ try:
695
+ os.unlink(tmp_filename)
696
+ except OSError:
697
+ pass
698
+ raise
689
699
  log.info("Done")
690
700
 
691
701
 
@@ -66,6 +66,7 @@ commands =
66
66
 
67
67
  [flake8]
68
68
  max-line-length = 160
69
+ extend-ignore = E203
69
70
  # test_github.py ignored because of https://gitlab.com/pycqa/flake8/issues/375
70
71
  exclude = .ropeproject,.tox,sandbox,docs,.eggs,*.egg,*.egg-info,setup.py,build/,tests/test_github.py,.venv
71
72
  per-file-ignores =
File without changes
File without changes
File without changes