docker-devkit 0.2.0.dev37277620738__tar.gz → 0.2.1.dev109__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 (44) hide show
  1. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/.github/actions/setup-release-devkit/action.yml +1 -1
  2. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/.github/workflows/integrate.yml +21 -0
  3. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/.github/workflows/merge-gate.yml +2 -3
  4. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/.github/workflows/release.yml +7 -4
  5. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/AGENTS.md +1 -1
  6. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/PKG-INFO +2 -2
  7. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/pyproject.toml +2 -2
  8. docker_devkit-0.2.1.dev109/release-devkit.yaml +6 -0
  9. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/build_docker.py +63 -35
  10. docker_devkit-0.2.1.dev109/tests/test_build_docker.py +92 -0
  11. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/uv.lock +4 -4
  12. docker_devkit-0.2.0.dev37277620738/release-devkit.yaml +0 -7
  13. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/.gitattributes +0 -0
  14. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/.gitignore +0 -0
  15. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/.python-version +0 -0
  16. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/CLAUDE.md +0 -0
  17. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/LICENSE +0 -0
  18. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/NOTICE +0 -0
  19. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/README.md +0 -0
  20. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/ruff.base.toml +0 -0
  21. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/ruff.toml +0 -0
  22. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/__init__.py +0 -0
  23. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/config.py +0 -0
  24. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/context_sha.py +0 -0
  25. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/detect_gpu.py +0 -0
  26. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/documents.py +0 -0
  27. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/down.py +0 -0
  28. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/image_refs.py +0 -0
  29. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/lifecycle.py +0 -0
  30. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/matrix.py +0 -0
  31. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/mirror.py +0 -0
  32. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/modes.py +0 -0
  33. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/py.typed +0 -0
  34. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/score_generate.py +0 -0
  35. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/up.py +0 -0
  36. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_config.py +0 -0
  37. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_context_sha.py +0 -0
  38. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_default_targets.py +0 -0
  39. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_documents.py +0 -0
  40. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_image_refs.py +0 -0
  41. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_lifecycle.py +0 -0
  42. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_matrix.py +0 -0
  43. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_mirror.py +0 -0
  44. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_score_generate.py +0 -0
@@ -5,7 +5,7 @@ runs:
5
5
  steps:
6
6
  - shell: bash
7
7
  env:
8
- RELEASE_DEVKIT_COMMIT: 1130b982aeb956a42b7bf9000ad231eaade593bf
8
+ RELEASE_DEVKIT_COMMIT: 84afa7a39ba21ab8971014dbefe130e3b9df8ded
9
9
  run: |
10
10
  git clone https://github.com/outernet-foundation/release-devkit.git "$RUNNER_TEMP/release-devkit"
11
11
  git -C "$RUNNER_TEMP/release-devkit" checkout "$RELEASE_DEVKIT_COMMIT"
@@ -54,3 +54,24 @@ jobs:
54
54
  - *uv-restore
55
55
  - *setup-release-devkit
56
56
  - run: uv run --project "$RUNNER_TEMP/release-devkit" --locked --no-dev validate-release-plan
57
+
58
+ update-pr-draft-release:
59
+ if: github.event.pull_request
60
+ needs: [validate-release-plan]
61
+ permissions:
62
+ contents: write
63
+ packages: read
64
+ runs-on: ubuntu-latest
65
+ steps:
66
+ - uses: actions/checkout@v5
67
+ with:
68
+ ref: ${{ github.event.pull_request.head.sha }}
69
+ # Perform a full clone, required for tags
70
+ fetch-depth: 0
71
+ fetch-tags: true
72
+ persist-credentials: false
73
+ - *uv-restore
74
+ - *setup-release-devkit
75
+ - run: uv run --project "$RUNNER_TEMP/release-devkit" --locked --no-dev release --channel pr
76
+ env:
77
+ GITHUB_TOKEN: ${{ github.token }}
@@ -22,7 +22,7 @@ jobs:
22
22
  - uses: actions/checkout@v5
23
23
  with:
24
24
  ref: ${{ github.event.pull_request.head.sha || github.event.workflow_run.head_sha }}
25
- # Perform a full clone, required so merge-gate can validate that this PR is a fast forward from dev
25
+ # Perform a full clone, required so merge-gate can validate that this PR is a semi-linear merge into dev
26
26
  fetch-depth: 0
27
27
  persist-credentials: false
28
28
  - uses: astral-sh/setup-uv@v7
@@ -35,7 +35,6 @@ jobs:
35
35
  with:
36
36
  app-id: ${{ vars.MERGE_BOT_APP_ID }}
37
37
  private-key: ${{ secrets.MERGE_BOT_APP_PRIVATE_KEY }}
38
- - run: uv run --project "$RUNNER_TEMP/release-devkit" --locked --no-dev merge-gate
38
+ - run: uv run --project "$RUNNER_TEMP/release-devkit" --locked --no-dev merge-gate --head-sha ${{ github.event.pull_request.head.sha || github.event.workflow_run.head_sha }}
39
39
  env:
40
40
  GITHUB_TOKEN: ${{ steps.mint.outputs.token }}
41
- HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.workflow_run.head_sha }}
@@ -11,12 +11,13 @@ permissions:
11
11
  contents: read
12
12
 
13
13
  jobs:
14
- publish-prerelease:
14
+ prerelease:
15
15
  if: github.ref == 'refs/heads/dev'
16
16
  runs-on: ubuntu-latest
17
17
  permissions:
18
- contents: read
18
+ contents: write
19
19
  id-token: write
20
+ packages: read
20
21
  steps:
21
22
  - uses: actions/checkout@v5
22
23
  with:
@@ -31,7 +32,9 @@ jobs:
31
32
  save-cache: "false"
32
33
  - &setup-release-devkit
33
34
  uses: ./.github/actions/setup-release-devkit
34
- - run: uv run --project "$RUNNER_TEMP/release-devkit" --locked --no-dev publish-prerelease
35
+ - run: uv run --project "$RUNNER_TEMP/release-devkit" --locked --no-dev release --channel dev
36
+ env:
37
+ GITHUB_TOKEN: ${{ github.token }}
35
38
 
36
39
  release:
37
40
  if: github.ref == 'refs/heads/main'
@@ -49,6 +52,6 @@ jobs:
49
52
  persist-credentials: true
50
53
  - *uv-restore
51
54
  - *setup-release-devkit
52
- - run: uv run --project "$RUNNER_TEMP/release-devkit" --locked --no-dev release
55
+ - run: uv run --project "$RUNNER_TEMP/release-devkit" --locked --no-dev release --channel stable
53
56
  env:
54
57
  GITHUB_TOKEN: ${{ github.token }}
@@ -65,7 +65,7 @@ The mirror is declared, not inferred: the `mirror:` section with `prefix` in the
65
65
  - The `mirror` verb (crane install from a pinned release, per-target `crane copy`, count footer) uses bashrun + plain prints, deliberately not ci-devkit's `ci_step` — it needs nothing from the floor.
66
66
  - Non-mirror lock entries (e.g. `placeframe/*` publishes) are not probed and keep failing naturally at pull. There is no config-level ban on the flag: an air-gap fails closed anyway (no route to upstream — the pull dies with or without it).
67
67
 
68
- The ci-devkit edge exists for the builds shelf: a CI `build` run pushes the resolved `workloads/images.lock` to the repo's builds registry (`--builds-registry` + `--lock-project` + `--run-number`) through ci-devkit's `push_build` — one `images-lock`-platform artifact tagged `run-{N}` per run, the exact lock the run baked with, consumed by release-devkit's release stapler. ORAS CLI knowledge lives only in ci-devkit (org law); local builds (`--mode local`, no `--builds-registry`) never touch it.
68
+ The ci-devkit edge exists for the builds shelf: a CI `build` run distills bake's `--metadata-file` into a digest manifest (`target → {ref, digest, tags[]}` JSON) and pushes it to the repo's builds shelf at the conventional `images-digests/all` project/platform through ci-devkit's `push_build` — one `images-digests-all` artifact tagged `sha-{full}` per CI head SHA, consumed by release-devkit's release surfaces for the built-image digest table. The shelf address and the SHA are derived in ci mode, never flagged: the address is `ghcr.io/{GITHUB_REPOSITORY}/builds` (the same ambient-derived constant release-devkit pulls from — a flag would carry no information and only add a typo surface), and the SHA is the checkout HEAD (`git rev-parse HEAD`), stamping the certified PR head the release channels query back at; the keying law is stated in release-devkit's `AGENTS.md`. `workloads/images.lock` stays in source control (the stapler that consumed the shelf copy is gone). ORAS CLI knowledge lives only in ci-devkit (org law); local builds (`--mode local`) never touch the shelf.
69
69
 
70
70
  Lifecycle stays a separate package from codegen (client generation, datamodel generation, workspace locking) because their dependency surfaces are disjoint — bundling them would force a consumer that only brings a stack up to install `sqlacodegen`, `psycopg`, `datamodel-code-generator`.
71
71
 
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: docker-devkit
3
- Version: 0.2.0.dev37277620738
3
+ Version: 0.2.1.dev109
4
4
  Summary: Docker-stack lifecycle commands (up/down/build) driven by a pyproject-declared compose assembly
5
5
  License-File: LICENSE
6
6
  License-File: NOTICE
7
7
  Requires-Python: >=3.13
8
8
  Requires-Dist: bashrun>=0.1.0
9
- Requires-Dist: ci-devkit==0.1.15.dev36756330648
9
+ Requires-Dist: ci-devkit>=0.1.15
10
10
  Requires-Dist: packaging>=24.1
11
11
  Requires-Dist: pathspec>=1.0.4
12
12
  Requires-Dist: pydantic-settings>=2.9.1
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "docker-devkit"
3
- version = "0.2.0.dev37277620738"
3
+ version = "0.2.1.dev109"
4
4
  description = "Docker-stack lifecycle commands (up/down/build) driven by a pyproject-declared compose assembly"
5
5
  requires-python = ">=3.13"
6
6
  dependencies = [
7
7
  "bashrun>=0.1.0",
8
- "ci-devkit==0.1.15.dev36756330648",
8
+ "ci-devkit>=0.1.15",
9
9
  "packaging>=24.1",
10
10
  "pathspec>=1.0.4",
11
11
  "pydantic>=2.11.7",
@@ -0,0 +1,6 @@
1
+ packages:
2
+ docker-devkit:
3
+ path: .
4
+ major_minor: "0.2"
5
+ registry: pypi
6
+ identity: docker-devkit
@@ -6,11 +6,13 @@ import platform
6
6
  import re
7
7
  from pathlib import Path
8
8
  from subprocess import CalledProcessError
9
- from typing import Annotated, Any, Literal
9
+ from tempfile import TemporaryDirectory
10
+ from typing import Annotated, Any, Literal, TypedDict
10
11
 
11
12
  import typer
12
13
  from bashrun.bash import bash, bash_output
13
14
  from ci_devkit.builds import push_build
15
+ from ci_devkit.setup_oras import install_oras
14
16
  from .detect_gpu import GPU_TYPES, Gpu, detect_gpu
15
17
  from .documents import BakeDocument, digest_pinned, parse_bake
16
18
  from pydantic_settings import BaseSettings
@@ -32,7 +34,16 @@ METADATA_PATH = Path("metadata.json")
32
34
 
33
35
  Mode = Literal["local", "ci"]
34
36
 
35
- IMAGES_LOCK_PLATFORM = "images-lock"
37
+ DIGEST_PROJECT = "images-digests"
38
+ DIGEST_PLATFORM = "all"
39
+ DIGEST_FILE_NAME = "images-digests.json"
40
+
41
+
42
+ class DigestEntry(TypedDict):
43
+ ref: str
44
+ digest: str
45
+ tags: list[str]
46
+
36
47
 
37
48
  app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
38
49
 
@@ -53,17 +64,6 @@ def build(
53
64
  list[str] | None,
54
65
  typer.Option("--targets", "-t", help="Build only these services (from the image manifest)."),
55
66
  ] = None,
56
- builds_registry: Annotated[
57
- str | None,
58
- typer.Option("--builds-registry", help="OCI registry path to push the resolved lock to (CI build artifacts)."),
59
- ] = None,
60
- lock_project: Annotated[
61
- str | None,
62
- typer.Option("--lock-project", help="Project component of the lock's builds-shelf address (CI only)."),
63
- ] = None,
64
- run_number: Annotated[
65
- int, typer.Option("--run-number", help="CI run number baked into the lock's builds-shelf tag.")
66
- ] = 0,
67
67
  ) -> None:
68
68
  run_build(
69
69
  upgrade=upgrade,
@@ -73,9 +73,6 @@ def build(
73
73
  gpu_only=gpu_only,
74
74
  no_cache=no_cache,
75
75
  targets_opt=targets_opt,
76
- builds_registry=builds_registry,
77
- lock_project=lock_project,
78
- run_number=run_number,
79
76
  )
80
77
 
81
78
 
@@ -89,9 +86,6 @@ def run_build(
89
86
  no_cache: bool = False,
90
87
  targets_opt: list[str] | None = None,
91
88
  env_overlay: dict[str, str] | None = None,
92
- builds_registry: str | None = None,
93
- lock_project: str | None = None,
94
- run_number: int = 0,
95
89
  ) -> None:
96
90
  root = Path.cwd()
97
91
  manifest = require_manifest(root)
@@ -223,22 +217,56 @@ def run_build(
223
217
  if not set(targets) <= baked_images.keys():
224
218
  raise RuntimeError("Bake output does not cover the requested targets")
225
219
 
226
- push_images_lock(builds_registry, lock_project, run_number, lock_file)
227
-
228
-
229
- def push_images_lock(builds_registry: str | None, lock_project: str | None, run_number: int, lock_file: Path) -> None:
230
- if builds_registry is None:
231
- return
232
- if not lock_project or run_number <= 0:
233
- raise typer.BadParameter("--builds-registry requires --lock-project and a positive --run-number")
234
- push_build(
235
- builds_registry,
236
- lock_project,
237
- IMAGES_LOCK_PLATFORM,
238
- f"run-{run_number}",
239
- lock_file.parent,
240
- [lock_file.name],
241
- )
220
+ # Push the digest manifest to the repo's builds shelf (address and SHA derived, never flagged)
221
+ if mode == "ci":
222
+ github_repository = os.environ.get("GITHUB_REPOSITORY", "")
223
+ if not github_repository:
224
+ raise SystemExit("GITHUB_REPOSITORY not set — ci mode reads the GitHub runner environment")
225
+ shelf = f"ghcr.io/{github_repository}/builds"
226
+ sha = bash_output("git rev-parse HEAD").strip()
227
+ push_image_digests(shelf, sha, baked_images, targets)
228
+
229
+
230
+ def push_image_digests(
231
+ shelf: str,
232
+ sha: str,
233
+ baked_images: dict[str, Any],
234
+ targets: list[str],
235
+ ) -> None:
236
+ install_oras()
237
+ manifest = distill_digest_manifest(baked_images, targets)
238
+ with TemporaryDirectory(prefix="digest-manifest-") as staging:
239
+ manifest_path = Path(staging) / DIGEST_FILE_NAME
240
+ manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8")
241
+ push_build(
242
+ shelf,
243
+ DIGEST_PROJECT,
244
+ DIGEST_PLATFORM,
245
+ f"sha-{sha}",
246
+ Path(staging),
247
+ [DIGEST_FILE_NAME],
248
+ )
249
+
250
+
251
+ def distill_digest_manifest(baked_images: dict[str, Any], targets: list[str]) -> dict[str, DigestEntry]:
252
+ manifest: dict[str, DigestEntry] = {}
253
+ for target in targets:
254
+ entry = baked_images[target]
255
+ image_name = entry["image.name"]
256
+ digest = entry["containerimage.digest"]
257
+ refs = [reference.strip() for reference in image_name.split(",") if reference.strip()]
258
+ image_ref = ""
259
+ tags: list[str] = []
260
+ for reference in refs:
261
+ reference = reference.split("@", 1)[0]
262
+ repository, separator, tag = reference.rpartition(":")
263
+ if separator:
264
+ image_ref = repository
265
+ tags.append(tag)
266
+ if not tags:
267
+ raise ValueError(f"target '{target}' produced no tagged image references from '{image_name}'")
268
+ manifest[target] = {"ref": image_ref, "digest": digest, "tags": tags}
269
+ return manifest
242
270
 
243
271
 
244
272
  # Vibe code - Gemini 3
@@ -0,0 +1,92 @@
1
+ import json
2
+ from pathlib import Path
3
+ from typing import Any
4
+
5
+ import pytest
6
+
7
+ from docker_devkit import build_docker
8
+ from docker_devkit.build_docker import DIGEST_FILE_NAME, distill_digest_manifest, push_image_digests
9
+
10
+ DIGEST_A = "sha256:" + "a" * 64
11
+ DIGEST_B = "sha256:" + "b" * 64
12
+ BUILD_SHA = "c" * 40
13
+ REPO_ZED = "ghcr.io/outernet-foundation/placeframe-capture-tool/zed-capture"
14
+ REPO_AOA = "ghcr.io/outernet-foundation/placeframe-capture-tool/aoa-bridge"
15
+
16
+
17
+ def _baked(target: str, *, refs: str, digest: str) -> dict[str, dict[str, str]]:
18
+ return {target: {"image.name": refs, "containerimage.digest": digest}}
19
+
20
+
21
+ class PushBuildRecorder:
22
+ def __init__(self) -> None:
23
+ self.calls: list[tuple[str, str, str, str, Path, list[str]]] = []
24
+ self.manifests: list[dict[str, Any]] = []
25
+
26
+ def __call__(
27
+ self,
28
+ registry: str,
29
+ project: str,
30
+ platform: str,
31
+ tag: str,
32
+ source_directory: Path,
33
+ paths: list[str],
34
+ ) -> None:
35
+ self.calls.append((registry, project, platform, tag, source_directory, paths))
36
+ manifest_path = Path(source_directory) / paths[0]
37
+ self.manifests.append(json.loads(manifest_path.read_text(encoding="utf-8")))
38
+
39
+
40
+ def test_distills_single_target_single_tag() -> None:
41
+ baked = _baked("zed-capture", refs=f"{REPO_ZED}:tree-abc", digest=DIGEST_A)
42
+ assert distill_digest_manifest(baked, ["zed-capture"]) == {
43
+ "zed-capture": {"ref": REPO_ZED, "digest": DIGEST_A, "tags": ["tree-abc"]}
44
+ }
45
+
46
+
47
+ def test_distills_multiple_tags_from_comma_separated_refs() -> None:
48
+ baked = _baked("zed-capture", refs=f"{REPO_ZED}:tree-abc,{REPO_ZED}:latest", digest=DIGEST_A)
49
+ assert distill_digest_manifest(baked, ["zed-capture"]) == {
50
+ "zed-capture": {"ref": REPO_ZED, "digest": DIGEST_A, "tags": ["tree-abc", "latest"]}
51
+ }
52
+
53
+
54
+ def test_distills_multiple_targets() -> None:
55
+ baked = {
56
+ "zed-capture": {"image.name": f"{REPO_ZED}:tree-abc", "containerimage.digest": DIGEST_A},
57
+ "aoa-bridge": {"image.name": f"{REPO_AOA}:tree-def", "containerimage.digest": DIGEST_B},
58
+ }
59
+ assert distill_digest_manifest(baked, ["zed-capture", "aoa-bridge"]) == {
60
+ "zed-capture": {"ref": REPO_ZED, "digest": DIGEST_A, "tags": ["tree-abc"]},
61
+ "aoa-bridge": {"ref": REPO_AOA, "digest": DIGEST_B, "tags": ["tree-def"]},
62
+ }
63
+
64
+
65
+ def test_strips_digest_suffix_from_refs() -> None:
66
+ baked = _baked("zed-capture", refs=f"{REPO_ZED}:tree-abc@{DIGEST_A}", digest=DIGEST_A)
67
+ entry = distill_digest_manifest(baked, ["zed-capture"])["zed-capture"]
68
+ assert entry["ref"] == REPO_ZED
69
+ assert entry["tags"] == ["tree-abc"]
70
+
71
+
72
+ def test_refuses_target_with_no_tagged_references() -> None:
73
+ baked = _baked("zed-capture", refs=f"{REPO_ZED}@{DIGEST_A}", digest=DIGEST_A)
74
+ with pytest.raises(ValueError, match="zed-capture"):
75
+ distill_digest_manifest(baked, ["zed-capture"])
76
+
77
+
78
+ def test_push_image_digests_pushes_manifest_to_images_digests_all(monkeypatch: pytest.MonkeyPatch) -> None:
79
+ recorder = PushBuildRecorder()
80
+ monkeypatch.setattr(build_docker, "push_build", recorder)
81
+ baked = _baked("zed-capture", refs=f"{REPO_ZED}:tree-abc,{REPO_ZED}:latest", digest=DIGEST_A)
82
+ push_image_digests("ghcr.io/owner/builds", BUILD_SHA, baked, ["zed-capture"])
83
+ assert len(recorder.calls) == 1
84
+ registry, project, platform, tag, _source_directory, paths = recorder.calls[0]
85
+ assert registry == "ghcr.io/owner/builds"
86
+ assert project == "images-digests"
87
+ assert platform == "all"
88
+ assert tag == f"sha-{BUILD_SHA}"
89
+ assert paths == [DIGEST_FILE_NAME]
90
+ assert recorder.manifests == [
91
+ {"zed-capture": {"ref": REPO_ZED, "digest": DIGEST_A, "tags": ["tree-abc", "latest"]}}
92
+ ]
@@ -43,16 +43,16 @@ wheels = [
43
43
 
44
44
  [[package]]
45
45
  name = "ci-devkit"
46
- version = "0.1.15.dev36756330648"
46
+ version = "0.2.0"
47
47
  source = { registry = "https://pypi.org/simple" }
48
48
  dependencies = [
49
49
  { name = "bashrun" },
50
50
  { name = "pydantic" },
51
51
  { name = "pydantic-settings" },
52
52
  ]
53
- sdist = { url = "https://files.pythonhosted.org/packages/5c/1e/fdad4f4fd539491f8ddeaf45e349801b274e9f7a8b00f768c6e83df1b97d/ci_devkit-0.1.15.dev36756330648.tar.gz", hash = "sha256:55b497b377a5e10e85c773b57897e4d1f883129b45567d1ecb581c02a18eabe4", size = 53675, upload-time = "2026-09-30T18:10:54.868Z" }
53
+ sdist = { url = "https://files.pythonhosted.org/packages/fd/c6/94c2a4a406b593ca3a1140151c0cb2c1367b30bf295f32a4bf9c92c100ac/ci_devkit-0.2.0.tar.gz", hash = "sha256:a16e0a91771a5b50205646577e8d56198c7c3262a063d7ebcf9a82fc26a2e3e0", size = 53288, upload-time = "2026-10-05T15:02:35.926Z" }
54
54
  wheels = [
55
- { url = "https://files.pythonhosted.org/packages/2f/1e/9f81e475c3cc65febb1e9013caf13dd20b393275c785713a7a85498dfcbc/ci_devkit-0.1.15.dev36756330648-py3-none-any.whl", hash = "sha256:4189adc38d182591cabef7e06321ae1105cdb9fb915f38d90d352a3f277630aa", size = 28208, upload-time = "2026-09-30T18:10:54.044Z" },
55
+ { url = "https://files.pythonhosted.org/packages/ce/d7/f7b91b7e62ed5cd7864f4ddbf84851ad5a31938342cb00e28bf15a7064c2/ci_devkit-0.2.0-py3-none-any.whl", hash = "sha256:d86403effbe36563b0a471d860e5e1e46ae7793d7d203a2dcc5561e91d1163f6", size = 28022, upload-time = "2026-10-05T15:02:34.654Z" },
56
56
  ]
57
57
 
58
58
  [[package]]
@@ -93,7 +93,7 @@ dev = [
93
93
  [package.metadata]
94
94
  requires-dist = [
95
95
  { name = "bashrun", specifier = ">=0.1.0" },
96
- { name = "ci-devkit", specifier = "==0.1.15.dev36756330648" },
96
+ { name = "ci-devkit", specifier = ">=0.1.15" },
97
97
  { name = "packaging", specifier = ">=24.1" },
98
98
  { name = "pathspec", specifier = ">=1.0.4" },
99
99
  { name = "pydantic", specifier = ">=2.11.7" },
@@ -1,7 +0,0 @@
1
- ci_workflow: release.yml
2
- packages:
3
- docker-devkit:
4
- path: .
5
- major_minor: "0.2"
6
- registries:
7
- pypi: docker-devkit