taskcluster-taskgraph 16.2.0__tar.gz → 16.2.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 (268) hide show
  1. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/CHANGELOG.md +9 -0
  2. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/PKG-INFO +2 -2
  3. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/pyproject.toml +2 -2
  4. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/run-task/run-task +21 -0
  5. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/taskcluster.py +2 -3
  6. taskcluster_taskgraph-16.2.1/test/test_util_archive.py +181 -0
  7. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_taskcluster.py +19 -4
  8. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/uv.lock +7 -7
  9. taskcluster_taskgraph-16.2.0/test/test_util_archive.py +0 -174
  10. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/.codespell-ignore-words.txt +0 -0
  11. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/.github/CODEOWNERS +0 -0
  12. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/.github/dependabot.yml +0 -0
  13. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/.github/workflows/codeql-analysis.yml +0 -0
  14. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/.github/workflows/pre-commit-autoupdate.yml +0 -0
  15. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/.github/workflows/pre-commit.yml +0 -0
  16. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/.github/workflows/pypi-publish.yml +0 -0
  17. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/.gitignore +0 -0
  18. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/.pre-commit-config.yaml +0 -0
  19. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/.readthedocs.yaml +0 -0
  20. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/.taskcluster.yml +0 -0
  21. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/.yamllint +0 -0
  22. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/CODE_OF_CONDUCT.md +0 -0
  23. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/CONTRIBUTING.rst +0 -0
  24. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/LICENSE +0 -0
  25. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/Makefile +0 -0
  26. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/README.rst +0 -0
  27. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/concepts/index.rst +0 -0
  28. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/concepts/kind.rst +0 -0
  29. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/concepts/loading.rst +0 -0
  30. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/concepts/optimization.rst +0 -0
  31. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/concepts/overview.rst +0 -0
  32. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/concepts/scopes.rst +0 -0
  33. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/concepts/task-graphs.rst +0 -0
  34. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/concepts/transforms.rst +0 -0
  35. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/conf.py +0 -0
  36. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/contributing.rst +0 -0
  37. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/glossary.rst +0 -0
  38. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/howto/bootstrap-taskgraph.rst +0 -0
  39. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/howto/create-actions.rst +0 -0
  40. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/howto/create-tasks.rst +0 -0
  41. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/howto/debugging.rst +0 -0
  42. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/howto/docker.rst +0 -0
  43. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/howto/index.rst +0 -0
  44. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/howto/load-task-locally.rst +0 -0
  45. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/howto/resolve-keyed-by.rst +0 -0
  46. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/howto/run-locally.rst +0 -0
  47. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/howto/send-notifications.rst +0 -0
  48. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/howto/use-fetches.rst +0 -0
  49. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/index.rst +0 -0
  50. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/cli.rst +0 -0
  51. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/index.rst +0 -0
  52. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/migrations.rst +0 -0
  53. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/optimization-strategies.rst +0 -0
  54. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/parameters.rst +0 -0
  55. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/source/modules.rst +0 -0
  56. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/source/taskgraph.actions.rst +0 -0
  57. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/source/taskgraph.loader.rst +0 -0
  58. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/source/taskgraph.optimize.rst +0 -0
  59. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/source/taskgraph.rst +0 -0
  60. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/source/taskgraph.transforms.rst +0 -0
  61. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/source/taskgraph.transforms.run.rst +0 -0
  62. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/source/taskgraph.util.rst +0 -0
  63. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/transforms/chunking.rst +0 -0
  64. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/transforms/from_deps.rst +0 -0
  65. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/transforms/index.rst +0 -0
  66. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/transforms/matrix.rst +0 -0
  67. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/reference/transforms/task_context.rst +0 -0
  68. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/tutorials/connecting-taskcluster.rst +0 -0
  69. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/tutorials/creating-a-task-graph.rst +0 -0
  70. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/tutorials/example-taskcluster.yml +0 -0
  71. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/tutorials/getting-started.rst +0 -0
  72. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/docs/tutorials/index.rst +0 -0
  73. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/make.bat +0 -0
  74. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/packages/pytest-taskgraph/README.md +0 -0
  75. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/packages/pytest-taskgraph/pyproject.toml +0 -0
  76. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/packages/pytest-taskgraph/src/pytest_taskgraph/__init__.py +0 -0
  77. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/packages/pytest-taskgraph/src/pytest_taskgraph/fixtures/gen.py +0 -0
  78. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/packages/pytest-taskgraph/src/pytest_taskgraph/fixtures/vcs.py +0 -0
  79. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/packages/sphinx-taskgraph/README.md +0 -0
  80. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/packages/sphinx-taskgraph/pyproject.toml +0 -0
  81. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/packages/sphinx-taskgraph/src/sphinx_taskgraph/__init__.py +0 -0
  82. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/packages/sphinx-taskgraph/src/sphinx_taskgraph/autoschema.py +0 -0
  83. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/__init__.py +0 -0
  84. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/actions/__init__.py +0 -0
  85. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/actions/add_new_jobs.py +0 -0
  86. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/actions/cancel.py +0 -0
  87. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/actions/cancel_all.py +0 -0
  88. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/actions/rebuild_cached_tasks.py +0 -0
  89. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/actions/registry.py +0 -0
  90. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/actions/retrigger.py +0 -0
  91. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/actions/util.py +0 -0
  92. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/config.py +0 -0
  93. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/create.py +0 -0
  94. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/decision.py +0 -0
  95. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/docker.py +0 -0
  96. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/filter_tasks.py +0 -0
  97. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/generator.py +0 -0
  98. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/graph.py +0 -0
  99. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/loader/__init__.py +0 -0
  100. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/loader/default.py +0 -0
  101. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/loader/transform.py +0 -0
  102. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/main.py +0 -0
  103. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/morph.py +0 -0
  104. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/optimize/__init__.py +0 -0
  105. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/optimize/base.py +0 -0
  106. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/optimize/strategies.py +0 -0
  107. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/parameters.py +0 -0
  108. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/run-task/fetch-content +0 -0
  109. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/run-task/hgrc +0 -0
  110. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/run-task/robustcheckout.py +0 -0
  111. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/target_tasks.py +0 -0
  112. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/task.py +0 -0
  113. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/taskgraph.py +0 -0
  114. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/transforms/__init__.py +0 -0
  115. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/transforms/base.py +0 -0
  116. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/transforms/cached_tasks.py +0 -0
  117. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/transforms/chunking.py +0 -0
  118. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/transforms/code_review.py +0 -0
  119. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/transforms/docker_image.py +0 -0
  120. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/transforms/fetch.py +0 -0
  121. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/transforms/from_deps.py +0 -0
  122. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/transforms/matrix.py +0 -0
  123. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/transforms/notify.py +0 -0
  124. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/transforms/run/__init__.py +0 -0
  125. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/transforms/run/common.py +0 -0
  126. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/transforms/run/index_search.py +0 -0
  127. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/transforms/run/run_task.py +0 -0
  128. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/transforms/run/toolchain.py +0 -0
  129. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/transforms/task.py +0 -0
  130. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/transforms/task_context.py +0 -0
  131. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/__init__.py +0 -0
  132. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/archive.py +0 -0
  133. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/attributes.py +0 -0
  134. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/cached_tasks.py +0 -0
  135. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/caches.py +0 -0
  136. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/copy.py +0 -0
  137. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/dependencies.py +0 -0
  138. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/docker.py +0 -0
  139. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/hash.py +0 -0
  140. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/json.py +0 -0
  141. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/keyed_by.py +0 -0
  142. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/parameterization.py +0 -0
  143. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/path.py +0 -0
  144. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/python_path.py +0 -0
  145. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/readonlydict.py +0 -0
  146. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/schema.py +0 -0
  147. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/set_name.py +0 -0
  148. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/shell.py +0 -0
  149. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/taskgraph.py +0 -0
  150. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/templates.py +0 -0
  151. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/time.py +0 -0
  152. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/treeherder.py +0 -0
  153. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/vcs.py +0 -0
  154. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/verify.py +0 -0
  155. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/workertypes.py +0 -0
  156. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/src/taskgraph/util/yaml.py +0 -0
  157. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/config.yml +0 -0
  158. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/REGISTRY +0 -0
  159. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/decision/Dockerfile +0 -0
  160. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/decision/HASH +0 -0
  161. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/decision/README.md +0 -0
  162. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/decision/VERSION +0 -0
  163. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/decision/system-setup.sh +0 -0
  164. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/fetch/Dockerfile +0 -0
  165. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/image_builder/README.rst +0 -0
  166. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/index-task/Dockerfile +0 -0
  167. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/index-task/README +0 -0
  168. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/index-task/insert-indexes.js +0 -0
  169. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/index-task/package.json +0 -0
  170. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/index-task/yarn.lock +0 -0
  171. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/python/Dockerfile +0 -0
  172. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/run-task/Dockerfile +0 -0
  173. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/run-task/system-setup.sh +0 -0
  174. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/skopeo/Dockerfile +0 -0
  175. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/skopeo/policy.json +0 -0
  176. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/docker/skopeo/push_image.sh +0 -0
  177. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/kinds/check/kind.yml +0 -0
  178. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/kinds/codecov/kind.yml +0 -0
  179. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/kinds/complete/kind.yml +0 -0
  180. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/kinds/doc/kind.yml +0 -0
  181. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/kinds/docker-image/kind.yml +0 -0
  182. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/kinds/fetch/kind.yml +0 -0
  183. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/kinds/push-image/kind.yml +0 -0
  184. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/kinds/test/kind.yml +0 -0
  185. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/scripts/codecov-upload.py +0 -0
  186. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/scripts/external_tools/tooltool.py +0 -0
  187. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/self_taskgraph/__init__.py +0 -0
  188. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/self_taskgraph/custom_parameters.py +0 -0
  189. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/self_taskgraph/custom_target_tasks.py +0 -0
  190. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/self_taskgraph/transforms/add_pr_route.py +0 -0
  191. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/self_taskgraph/transforms/push_image.py +0 -0
  192. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/test/params/main-repo-pull-request-untrusted.yml +0 -0
  193. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/test/params/main-repo-pull-request.yml +0 -0
  194. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/test/params/main-repo-push.yml +0 -0
  195. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/test/params/main-repo-release-pytest-taskgraph.yml +0 -0
  196. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/taskcluster/test/params/main-repo-release-taskcluster-taskgraph.yml +0 -0
  197. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/template/cookiecutter.json +0 -0
  198. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/template/hooks/post_gen_project.py +0 -0
  199. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/template/{{cookiecutter.project_name}}/taskcluster/config.yml +0 -0
  200. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/template/{{cookiecutter.project_name}}/taskcluster/docker/linux/Dockerfile +0 -0
  201. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/template/{{cookiecutter.project_name}}/taskcluster/kinds/docker-image/kind.yml +0 -0
  202. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/template/{{cookiecutter.project_name}}/taskcluster/kinds/hello/kind.yml +0 -0
  203. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/template/{{cookiecutter.project_name}}/taskcluster/{{cookiecutter.project_slug}}_taskgraph/transforms/hello.py +0 -0
  204. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/template/{{cookiecutter.project_name}}/taskcluster.github.yml +0 -0
  205. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/__init__.py +0 -0
  206. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/automationrelevance.json +0 -0
  207. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/conftest.py +0 -0
  208. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/data/task_context.yml +0 -0
  209. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/data/taskcluster/config.yml +0 -0
  210. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/data/taskcluster/docker/hello-world/Dockerfile +0 -0
  211. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/data/taskcluster/docker/hello-world-tag/Dockerfile +0 -0
  212. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/data/taskcluster/docker/hello-world-tag/REGISTRY +0 -0
  213. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/data/taskcluster/docker/hello-world-tag/VERSION +0 -0
  214. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/data/taskcluster/kinds/docker-image/kind.yml +0 -0
  215. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/data/taskcluster/scripts/toolchain/run.ps1 +0 -0
  216. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/data/taskcluster/scripts/toolchain/run.sh +0 -0
  217. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/data/taskcluster/test_taskgraph/transforms/foo.py +0 -0
  218. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/data/testmod/thing.py +0 -0
  219. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/mockedopen.py +0 -0
  220. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_actions_rebuild_cached_tasks.py +0 -0
  221. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_actions_registry.py +0 -0
  222. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_config.py +0 -0
  223. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_create.py +0 -0
  224. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_decision.py +0 -0
  225. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_docker.py +0 -0
  226. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_generator.py +0 -0
  227. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_graph.py +0 -0
  228. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_main.py +0 -0
  229. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_morph.py +0 -0
  230. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_optimize.py +0 -0
  231. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_optimize_strategies.py +0 -0
  232. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_parameters.py +0 -0
  233. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_scripts_fetch_content.py +0 -0
  234. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_scripts_run_task.py +0 -0
  235. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_target_tasks.py +0 -0
  236. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_taskgraph.py +0 -0
  237. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_transform_chunking.py +0 -0
  238. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_transform_docker_image.py +0 -0
  239. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_transform_task_context.py +0 -0
  240. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_transforms_base.py +0 -0
  241. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_transforms_cached_tasks.py +0 -0
  242. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_transforms_fetch.py +0 -0
  243. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_transforms_from_deps.py +0 -0
  244. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_transforms_matrix.py +0 -0
  245. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_transforms_notify.py +0 -0
  246. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_transforms_run.py +0 -0
  247. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_transforms_run_run_task.py +0 -0
  248. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_transforms_run_toolchain.py +0 -0
  249. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_transforms_task.py +0 -0
  250. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_attributes.py +0 -0
  251. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_cached_tasks.py +0 -0
  252. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_copy.py +0 -0
  253. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_dependencies.py +0 -0
  254. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_docker.py +0 -0
  255. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_json.py +0 -0
  256. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_keyed_by.py +0 -0
  257. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_parameterization.py +0 -0
  258. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_path.py +0 -0
  259. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_python_path.py +0 -0
  260. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_readonlydict.py +0 -0
  261. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_schema.py +0 -0
  262. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_templates.py +0 -0
  263. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_time.py +0 -0
  264. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_treeherder.py +0 -0
  265. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_vcs.py +0 -0
  266. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_verify.py +0 -0
  267. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_workertypes.py +0 -0
  268. {taskcluster_taskgraph-16.2.0 → taskcluster_taskgraph-16.2.1}/test/test_util_yaml.py +0 -0
@@ -1,4 +1,13 @@
1
1
  # Change Log
2
+
3
+ ## [16.2.1] - 2025-10-14
4
+
5
+ ### Fixed
6
+
7
+ - `run-task` script now cleans up zombie processes
8
+ - Fixed regression to `taskgraph.util.taskcluster.list_artifacts`
9
+ - Fixed regression to `taskgraph.util.taskcluster.get_artifact`
10
+
2
11
  ## [16.2.0] - 2025-09-26
3
12
 
4
13
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: taskcluster-taskgraph
3
- Version: 16.2.0
3
+ Version: 16.2.1
4
4
  Summary: Build taskcluster taskgraphs
5
5
  Project-URL: Repository, https://github.com/taskcluster/taskgraph
6
6
  Project-URL: Issues, https://github.com/taskcluster/taskgraph/issues
@@ -26,7 +26,7 @@ Requires-Dist: redo>=2.0
26
26
  Requires-Dist: requests>=2.25
27
27
  Requires-Dist: slugid>=2.0
28
28
  Requires-Dist: taskcluster-urls>=11.0
29
- Requires-Dist: taskcluster>=55.0
29
+ Requires-Dist: taskcluster>=91.0
30
30
  Requires-Dist: voluptuous>=0.12.1
31
31
  Provides-Extra: load-image
32
32
  Requires-Dist: zstandard; extra == 'load-image'
@@ -1,7 +1,7 @@
1
1
  ### Project
2
2
  [project]
3
3
  name = "taskcluster-taskgraph"
4
- version = "16.2.0"
4
+ version = "16.2.1"
5
5
  description = "Build taskcluster taskgraphs"
6
6
  readme = "README.rst"
7
7
  authors = [
@@ -29,7 +29,7 @@ dependencies = [
29
29
  "redo>=2.0",
30
30
  "requests>=2.25",
31
31
  "slugid>=2.0",
32
- "taskcluster>=55.0",
32
+ "taskcluster>=91.0",
33
33
  "taskcluster-urls>=11.0",
34
34
  "voluptuous>=0.12.1",
35
35
  ]
@@ -28,6 +28,7 @@ import socket
28
28
  import stat
29
29
  import subprocess
30
30
  import sys
31
+ import threading
31
32
  import time
32
33
  import urllib.error
33
34
  import urllib.request
@@ -121,6 +122,17 @@ def print_line(prefix, m):
121
122
  sys.stdout.buffer.flush()
122
123
 
123
124
 
125
+ def reap_zombies(main_subprocess):
126
+ """Wait for main_subprocess to exit, while awaiting any other child processes"""
127
+ while main_subprocess.poll() is None:
128
+ with main_subprocess._waitpid_lock:
129
+ if main_subprocess.returncode is not None:
130
+ return
131
+ pid, status = os.wait()
132
+ if pid == main_subprocess.pid:
133
+ main_subprocess._handle_exitstatus(status)
134
+
135
+
124
136
  def _call_windows_retry(func, args=(), retry_max=5, retry_delay=0.5):
125
137
  """
126
138
  It's possible to see spurious errors on Windows due to various things
@@ -278,6 +290,13 @@ def run_command(prefix, args, *, extra_env=None, cwd=None):
278
290
 
279
291
  stdout = io.TextIOWrapper(p.stdout, encoding="latin1")
280
292
 
293
+ if os.getpid() == 1:
294
+ # in docker we're init, so we get to adopt unawaited zombies
295
+ reaper_thread = threading.Thread(target=reap_zombies, args=(p,))
296
+ reaper_thread.start()
297
+ else:
298
+ reaper_thread = None
299
+
281
300
  while True:
282
301
  data = stdout.readline().encode("latin1")
283
302
 
@@ -286,6 +305,8 @@ def run_command(prefix, args, *, extra_env=None, cwd=None):
286
305
 
287
306
  print_line(prefix, data)
288
307
 
308
+ if reaper_thread:
309
+ reaper_thread.join()
289
310
  return p.wait()
290
311
 
291
312
 
@@ -175,9 +175,8 @@ def get_artifact(task_id, path):
175
175
 
176
176
  def list_artifacts(task_id):
177
177
  queue = get_taskcluster_client("queue")
178
- task = queue.task(task_id)
179
- if task:
180
- return task["artifacts"]
178
+ response = queue.listLatestArtifacts(task_id)
179
+ return response["artifacts"]
181
180
 
182
181
 
183
182
  def get_artifact_prefix(task):
@@ -0,0 +1,181 @@
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
+ import hashlib
6
+ import io
7
+ import stat
8
+ import tarfile
9
+
10
+ import pytest
11
+
12
+ from taskgraph.util.archive import (
13
+ DEFAULT_MTIME,
14
+ create_tar_from_files,
15
+ create_tar_gz_from_files,
16
+ )
17
+
18
+ MODE_STANDARD = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
19
+
20
+
21
+ def file_hash(path):
22
+ h = hashlib.sha1()
23
+ with open(path, "rb") as fh:
24
+ while True:
25
+ data = fh.read(8192)
26
+ if not data:
27
+ break
28
+ h.update(data)
29
+
30
+ return h.hexdigest()
31
+
32
+
33
+ @pytest.fixture
34
+ def create_files(tmp_path):
35
+ def inner():
36
+ files = {}
37
+ for i in range(10):
38
+ p = tmp_path / f"file{i:02d}"
39
+ p.write_bytes(b"file%02d" % i)
40
+ # Need to set permissions or umask may influence testing.
41
+ p.chmod(MODE_STANDARD)
42
+ files[f"file{i:02d}"] = str(p)
43
+
44
+ for i in range(10):
45
+ files[f"file{i + 10:02d}"] = io.BytesIO(b"file%02d" % (i + 10))
46
+
47
+ return files
48
+
49
+ return inner
50
+
51
+
52
+ def verify_basic_tarfile(tf):
53
+ assert len(tf.getmembers()) == 20
54
+
55
+ names = [f"file{i:02d}" for i in range(20)]
56
+ assert tf.getnames() == names
57
+
58
+ for ti in tf.getmembers():
59
+ assert ti.uid == 0
60
+ assert ti.gid == 0
61
+ assert ti.uname == ""
62
+ assert ti.gname == ""
63
+ assert ti.mode == MODE_STANDARD
64
+ assert ti.mtime == DEFAULT_MTIME
65
+
66
+
67
+ @pytest.mark.xfail(reason="ValueError is not thrown despite being provided directory.")
68
+ def test_dirs_refused(tmp_path):
69
+ tp = tmp_path / "test.tar"
70
+ with open(tp, "wb") as fh:
71
+ with pytest.raises(ValueError, match="not a regular"):
72
+ create_tar_from_files(fh, {"test": str(tmp_path)})
73
+
74
+
75
+ def test_setuid_setgid_refused(tmp_path):
76
+ uid = tmp_path / "setuid"
77
+ uid.touch()
78
+ uid.chmod(MODE_STANDARD | stat.S_ISUID)
79
+
80
+ gid = tmp_path / "setgid"
81
+ gid.touch()
82
+ gid.chmod(MODE_STANDARD | stat.S_ISGID)
83
+
84
+ tp = tmp_path / "test.tar"
85
+ with open(tp, "wb") as fh:
86
+ with pytest.raises(ValueError, match="cannot add file with setuid"):
87
+ create_tar_from_files(fh, {"test": str(uid)})
88
+ with pytest.raises(ValueError, match="cannot add file with setuid"):
89
+ create_tar_from_files(fh, {"test": str(gid)})
90
+
91
+
92
+ def test_create_tar_basic(tmp_path, create_files):
93
+ files = create_files()
94
+
95
+ tp = tmp_path / "test.tar"
96
+ with open(tp, "wb") as fh:
97
+ create_tar_from_files(fh, files)
98
+
99
+ # Output should be deterministic.
100
+ assert file_hash(tp) == "01cd314e277f060e98c7de6c8ea57f96b3a2065c"
101
+
102
+ with tarfile.open(tp, "r") as tf:
103
+ verify_basic_tarfile(tf)
104
+
105
+
106
+ def test_executable_preserved(tmp_path):
107
+ p = tmp_path / "exec"
108
+ p.write_bytes(b"#!/bin/bash\n")
109
+ p.chmod(MODE_STANDARD | stat.S_IXUSR)
110
+
111
+ tp = tmp_path / "test.tar"
112
+ with open(tp, "wb") as fh:
113
+ create_tar_from_files(fh, {"exec": str(p)})
114
+
115
+ # Test determinism by creating the same file again
116
+ tp2 = tmp_path / "test2.tar"
117
+ with open(tp2, "wb") as fh:
118
+ create_tar_from_files(fh, {"exec": str(p)})
119
+
120
+ assert file_hash(str(tp)) == file_hash(str(tp2))
121
+
122
+ # Verify executable permissions are preserved in tar
123
+ with tarfile.open(tp, "r") as tf:
124
+ m = tf.getmember("exec")
125
+ assert m.mode == MODE_STANDARD | stat.S_IXUSR
126
+
127
+ # Verify file content is correct
128
+ extracted_content = tf.extractfile(m).read()
129
+ assert extracted_content == b"#!/bin/bash\n"
130
+
131
+
132
+ def test_create_tar_gz_basic(tmp_path, create_files):
133
+ gp = tmp_path / "test.tar.gz"
134
+ with open(gp, "wb") as fh:
135
+ create_tar_gz_from_files(fh, create_files())
136
+
137
+ # Test determinism by creating the same file again with fresh BytesIO objects
138
+ gp2 = tmp_path / "test2.tar.gz"
139
+ with open(gp2, "wb") as fh:
140
+ create_tar_gz_from_files(fh, create_files())
141
+
142
+ assert file_hash(str(gp)) == file_hash(str(gp2))
143
+
144
+ # Create uncompressed version for size comparison
145
+ tp = tmp_path / "test.tar"
146
+ with open(tp, "wb") as fh:
147
+ create_tar_from_files(fh, create_files())
148
+ uncompressed_size = tp.stat().st_size
149
+ compressed_size = gp.stat().st_size
150
+
151
+ # Compressed should be smaller than uncompressed
152
+ assert compressed_size < uncompressed_size
153
+
154
+ # Verify the contents are correct
155
+ with tarfile.open(gp, "r:gz") as tf:
156
+ verify_basic_tarfile(tf)
157
+
158
+
159
+ def test_tar_gz_name(tmp_path, create_files):
160
+ gp = tmp_path / "test.tar.gz"
161
+ with open(gp, "wb") as fh:
162
+ create_tar_gz_from_files(fh, create_files(), filename="foobar")
163
+
164
+ # Test determinism by creating the same file again with fresh BytesIO objects
165
+ gp2 = tmp_path / "test2.tar.gz"
166
+ with open(gp2, "wb") as fh:
167
+ create_tar_gz_from_files(fh, create_files(), filename="foobar")
168
+
169
+ assert file_hash(str(gp)) == file_hash(str(gp2))
170
+
171
+ # Create version without filename for comparison
172
+ gp_no_name = tmp_path / "test_no_name.tar.gz"
173
+ with open(gp_no_name, "wb") as fh:
174
+ create_tar_gz_from_files(fh, create_files())
175
+
176
+ # Files should be different (different filename in gzip header)
177
+ assert file_hash(str(gp)) != file_hash(str(gp_no_name))
178
+
179
+ # Verify the contents are correct
180
+ with tarfile.open(gp, "r:gz") as tf:
181
+ verify_basic_tarfile(tf)
@@ -110,16 +110,25 @@ def test_get_artifact(responses, root_url):
110
110
 
111
111
  # Test text artifact
112
112
  responses.get(
113
- f"{root_url}/api/queue/v1/task/{tid}/artifacts/artifact.txt",
113
+ "http://foo.bar/artifact.txt",
114
114
  body=b"foobar",
115
115
  )
116
+ responses.get(
117
+ f"{root_url}/api/queue/v1/task/{tid}/artifacts/artifact.txt",
118
+ body=b'{"type": "s3", "url": "http://foo.bar/artifact.txt"}',
119
+ status=303,
120
+ headers={"Location": "http://foo.bar/artifact.txt"},
121
+ )
116
122
  raw = tc.get_artifact(tid, "artifact.txt")
117
123
  assert raw.read() == b"foobar"
118
124
 
119
125
  # Test JSON artifact
126
+ responses.get("http://foo.bar/artifact.json", json={"foo": "bar"})
120
127
  responses.get(
121
128
  f"{root_url}/api/queue/v1/task/{tid}/artifacts/artifact.json",
122
- json={"foo": "bar"},
129
+ body=b'{"type": "s3", "url": "http://foo.bar/artifact.json"}',
130
+ status=303,
131
+ headers={"Location": "http://foo.bar/artifact.json"},
123
132
  )
124
133
  result = tc.get_artifact(tid, "artifact.json")
125
134
  assert result == {"foo": "bar"}
@@ -127,9 +136,15 @@ def test_get_artifact(responses, root_url):
127
136
  # Test YAML artifact
128
137
  expected_result = {"foo": b"\xe2\x81\x83".decode()}
129
138
  responses.get(
130
- f"{root_url}/api/queue/v1/task/{tid}/artifacts/artifact.yml",
139
+ "http://foo.bar/artifact.yml",
131
140
  body=b'foo: "\xe2\x81\x83"',
132
141
  )
142
+ responses.get(
143
+ f"{root_url}/api/queue/v1/task/{tid}/artifacts/artifact.yml",
144
+ body=b'{"type": "s3", "url": "http://foo.bar/artifact.yml"}',
145
+ status=303,
146
+ headers={"Location": "http://foo.bar/artifact.yml"},
147
+ )
133
148
  result = tc.get_artifact(tid, "artifact.yml")
134
149
  assert result == expected_result
135
150
 
@@ -139,7 +154,7 @@ def test_list_artifact(responses, root_url):
139
154
  tc.get_taskcluster_client.cache_clear()
140
155
 
141
156
  responses.get(
142
- f"{root_url}/api/queue/v1/task/{tid}",
157
+ f"{root_url}/api/queue/v1/task/{tid}/artifacts",
143
158
  json={"artifacts": ["file1.txt", "file2.json"]},
144
159
  )
145
160
 
@@ -1,5 +1,5 @@
1
1
  version = 1
2
- revision = 2
2
+ revision = 3
3
3
  requires-python = ">=3.8"
4
4
  resolution-markers = [
5
5
  "python_full_version >= '3.11'",
@@ -998,7 +998,7 @@ version = "1.3.0"
998
998
  source = { registry = "https://pypi.org/simple" }
999
999
  dependencies = [
1000
1000
  { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
1001
- { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" },
1001
+ { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" },
1002
1002
  ]
1003
1003
  sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
1004
1004
  wheels = [
@@ -3320,7 +3320,7 @@ wheels = [
3320
3320
 
3321
3321
  [[package]]
3322
3322
  name = "taskcluster"
3323
- version = "90.0.5"
3323
+ version = "91.0.0"
3324
3324
  source = { registry = "https://pypi.org/simple" }
3325
3325
  dependencies = [
3326
3326
  { name = "aiohttp", version = "3.10.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
@@ -3333,14 +3333,14 @@ dependencies = [
3333
3333
  { name = "slugid" },
3334
3334
  { name = "taskcluster-urls" },
3335
3335
  ]
3336
- sdist = { url = "https://files.pythonhosted.org/packages/7a/9a/f28142083e024bce75ebbc6ac5de0b3ce8a0b5989c83493a172718fde986/taskcluster-90.0.5.tar.gz", hash = "sha256:3529b62d05b3a869669e7c128cf46849de6641d50d598e8ce81cd4ed7ba756f6", size = 129199, upload-time = "2025-10-08T13:55:23.916Z" }
3336
+ sdist = { url = "https://files.pythonhosted.org/packages/64/b9/96fc43251b42348b2e422a45bd7c6795311e32e322b141e97be83ba28e64/taskcluster-91.0.0.tar.gz", hash = "sha256:2e21feac068750503a32f4ca4273c904d8455d6b1db8e7295c3aa119af65abf4", size = 129244, upload-time = "2025-10-14T17:13:33.602Z" }
3337
3337
  wheels = [
3338
- { url = "https://files.pythonhosted.org/packages/31/4c/7c4473080e9e74666e49fa3477f601d7188bb6bd1db68ec5e4c875829505/taskcluster-90.0.5-py3-none-any.whl", hash = "sha256:d4ca28ffa391fbd2f6553b5c390a606a01748ba12791ad90675efd65b734a3c7", size = 147341, upload-time = "2025-10-08T13:55:21.866Z" },
3338
+ { url = "https://files.pythonhosted.org/packages/2c/16/92be916f30d90ae41ee406a843f42bbedbe95109d61cb188187ab72bad75/taskcluster-91.0.0-py3-none-any.whl", hash = "sha256:c6301a5f730bb65feb2811b1190bef8124af0aeddd931eb877e066202348f5bd", size = 147340, upload-time = "2025-10-14T17:13:31.658Z" },
3339
3339
  ]
3340
3340
 
3341
3341
  [[package]]
3342
3342
  name = "taskcluster-taskgraph"
3343
- version = "16.2"
3343
+ version = "16.2.1"
3344
3344
  source = { editable = "." }
3345
3345
  dependencies = [
3346
3346
  { name = "appdirs" },
@@ -3411,7 +3411,7 @@ requires-dist = [
3411
3411
  { name = "redo", specifier = ">=2.0" },
3412
3412
  { name = "requests", specifier = ">=2.25" },
3413
3413
  { name = "slugid", specifier = ">=2.0" },
3414
- { name = "taskcluster", specifier = ">=55.0" },
3414
+ { name = "taskcluster", specifier = ">=91.0" },
3415
3415
  { name = "taskcluster-urls", specifier = ">=11.0" },
3416
3416
  { name = "voluptuous", specifier = ">=0.12.1" },
3417
3417
  { name = "zstandard", marker = "extra == 'load-image'" },
@@ -1,174 +0,0 @@
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
- import hashlib
6
- import io
7
- import os
8
- import shutil
9
- import stat
10
- import tarfile
11
- import tempfile
12
- import unittest
13
-
14
- import pytest
15
-
16
- from taskgraph.util.archive import (
17
- DEFAULT_MTIME,
18
- create_tar_from_files,
19
- create_tar_gz_from_files,
20
- )
21
-
22
- MODE_STANDARD = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
23
-
24
-
25
- def file_hash(path):
26
- h = hashlib.sha1()
27
- with open(path, "rb") as fh:
28
- while True:
29
- data = fh.read(8192)
30
- if not data:
31
- break
32
- h.update(data)
33
-
34
- return h.hexdigest()
35
-
36
-
37
- class TestArchive(unittest.TestCase):
38
- def _create_files(self, root):
39
- files = {}
40
- for i in range(10):
41
- p = os.path.join(root, f"file{i:02d}")
42
- with open(p, "wb") as fh:
43
- fh.write(b"file%02d" % i)
44
- # Need to set permissions or umask may influence testing.
45
- os.chmod(p, MODE_STANDARD)
46
- files[f"file{i:02d}"] = p
47
-
48
- for i in range(10):
49
- files[f"file{i + 10:02d}"] = io.BytesIO(b"file%02d" % (i + 10))
50
-
51
- return files
52
-
53
- def _verify_basic_tarfile(self, tf):
54
- self.assertEqual(len(tf.getmembers()), 20)
55
-
56
- names = [f"file{i:02d}" for i in range(20)]
57
- self.assertEqual(tf.getnames(), names)
58
-
59
- for ti in tf.getmembers():
60
- self.assertEqual(ti.uid, 0)
61
- self.assertEqual(ti.gid, 0)
62
- self.assertEqual(ti.uname, "")
63
- self.assertEqual(ti.gname, "")
64
- self.assertEqual(ti.mode, MODE_STANDARD)
65
- self.assertEqual(ti.mtime, DEFAULT_MTIME)
66
-
67
- @pytest.mark.xfail(
68
- reason="ValueError is not thrown despite being provided directory."
69
- )
70
- def test_dirs_refused(self):
71
- d = tempfile.mkdtemp()
72
- try:
73
- tp = os.path.join(d, "test.tar")
74
- with open(tp, "wb") as fh:
75
- with self.assertRaisesRegex(ValueError, "not a regular"):
76
- create_tar_from_files(fh, {"test": d})
77
- finally:
78
- shutil.rmtree(d)
79
-
80
- def test_setuid_setgid_refused(self):
81
- d = tempfile.mkdtemp()
82
- try:
83
- uid = os.path.join(d, "setuid")
84
- gid = os.path.join(d, "setgid")
85
- with open(uid, "a"):
86
- pass
87
- with open(gid, "a"):
88
- pass
89
-
90
- os.chmod(uid, MODE_STANDARD | stat.S_ISUID)
91
- os.chmod(gid, MODE_STANDARD | stat.S_ISGID)
92
-
93
- tp = os.path.join(d, "test.tar")
94
- with open(tp, "wb") as fh:
95
- with self.assertRaisesRegex(ValueError, "cannot add file with setuid"):
96
- create_tar_from_files(fh, {"test": uid})
97
- with self.assertRaisesRegex(ValueError, "cannot add file with setuid"):
98
- create_tar_from_files(fh, {"test": gid})
99
- finally:
100
- shutil.rmtree(d)
101
-
102
- def test_create_tar_basic(self):
103
- d = tempfile.mkdtemp()
104
- try:
105
- files = self._create_files(d)
106
-
107
- tp = os.path.join(d, "test.tar")
108
- with open(tp, "wb") as fh:
109
- create_tar_from_files(fh, files)
110
-
111
- # Output should be deterministic.
112
- self.assertEqual(file_hash(tp), "01cd314e277f060e98c7de6c8ea57f96b3a2065c")
113
-
114
- with tarfile.open(tp, "r") as tf:
115
- self._verify_basic_tarfile(tf)
116
-
117
- finally:
118
- shutil.rmtree(d)
119
-
120
- @pytest.mark.xfail(reason="hash mismatch")
121
- def test_executable_preserved(self):
122
- d = tempfile.mkdtemp()
123
- try:
124
- p = os.path.join(d, "exec")
125
- with open(p, "wb") as fh:
126
- fh.write("#!/bin/bash\n")
127
- os.chmod(p, MODE_STANDARD | stat.S_IXUSR)
128
-
129
- tp = os.path.join(d, "test.tar")
130
- with open(tp, "wb") as fh:
131
- create_tar_from_files(fh, {"exec": p})
132
-
133
- self.assertEqual(file_hash(tp), "357e1b81c0b6cfdfa5d2d118d420025c3c76ee93")
134
-
135
- with tarfile.open(tp, "r") as tf:
136
- m = tf.getmember("exec")
137
- self.assertEqual(m.mode, MODE_STANDARD | stat.S_IXUSR)
138
-
139
- finally:
140
- shutil.rmtree(d)
141
-
142
- def test_create_tar_gz_basic(self):
143
- d = tempfile.mkdtemp()
144
- try:
145
- files = self._create_files(d)
146
-
147
- gp = os.path.join(d, "test.tar.gz")
148
- with open(gp, "wb") as fh:
149
- create_tar_gz_from_files(fh, files)
150
-
151
- self.assertEqual(file_hash(gp), "7c4da5adc5088cdf00911d5daf9a67b15de714b7")
152
-
153
- with tarfile.open(gp, "r:gz") as tf:
154
- self._verify_basic_tarfile(tf)
155
-
156
- finally:
157
- shutil.rmtree(d)
158
-
159
- def test_tar_gz_name(self):
160
- d = tempfile.mkdtemp()
161
- try:
162
- files = self._create_files(d)
163
-
164
- gp = os.path.join(d, "test.tar.gz")
165
- with open(gp, "wb") as fh:
166
- create_tar_gz_from_files(fh, files, filename="foobar")
167
-
168
- self.assertEqual(file_hash(gp), "721e00083c17d16df2edbddf40136298c06d0c49")
169
-
170
- with tarfile.open(gp, "r:gz") as tf:
171
- self._verify_basic_tarfile(tf)
172
-
173
- finally:
174
- shutil.rmtree(d)