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.
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/.github/actions/setup-release-devkit/action.yml +1 -1
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/.github/workflows/integrate.yml +21 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/.github/workflows/merge-gate.yml +2 -3
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/.github/workflows/release.yml +7 -4
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/AGENTS.md +1 -1
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/PKG-INFO +2 -2
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/pyproject.toml +2 -2
- docker_devkit-0.2.1.dev109/release-devkit.yaml +6 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/build_docker.py +63 -35
- docker_devkit-0.2.1.dev109/tests/test_build_docker.py +92 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/uv.lock +4 -4
- docker_devkit-0.2.0.dev37277620738/release-devkit.yaml +0 -7
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/.gitattributes +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/.gitignore +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/.python-version +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/CLAUDE.md +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/LICENSE +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/NOTICE +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/README.md +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/ruff.base.toml +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/ruff.toml +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/__init__.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/config.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/context_sha.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/detect_gpu.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/documents.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/down.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/image_refs.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/lifecycle.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/matrix.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/mirror.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/modes.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/py.typed +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/score_generate.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/up.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_config.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_context_sha.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_default_targets.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_documents.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_image_refs.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_lifecycle.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_matrix.py +0 -0
- {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_mirror.py +0 -0
- {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:
|
|
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"
|
{docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/.github/workflows/integrate.yml
RENAMED
|
@@ -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 }}
|
{docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/.github/workflows/merge-gate.yml
RENAMED
|
@@ -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
|
|
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 }}
|
{docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/.github/workflows/release.yml
RENAMED
|
@@ -11,12 +11,13 @@ permissions:
|
|
|
11
11
|
contents: read
|
|
12
12
|
|
|
13
13
|
jobs:
|
|
14
|
-
|
|
14
|
+
prerelease:
|
|
15
15
|
if: github.ref == 'refs/heads/dev'
|
|
16
16
|
runs-on: ubuntu-latest
|
|
17
17
|
permissions:
|
|
18
|
-
contents:
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
8
|
+
"ci-devkit>=0.1.15",
|
|
9
9
|
"packaging>=24.1",
|
|
10
10
|
"pathspec>=1.0.4",
|
|
11
11
|
"pydantic>=2.11.7",
|
{docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/build_docker.py
RENAMED
|
@@ -6,11 +6,13 @@ import platform
|
|
|
6
6
|
import re
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from subprocess import CalledProcessError
|
|
9
|
-
from
|
|
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
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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.
|
|
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/
|
|
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/
|
|
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 = "
|
|
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" },
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/__init__.py
RENAMED
|
File without changes
|
{docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/config.py
RENAMED
|
File without changes
|
{docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/context_sha.py
RENAMED
|
File without changes
|
{docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/detect_gpu.py
RENAMED
|
File without changes
|
{docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/documents.py
RENAMED
|
File without changes
|
|
File without changes
|
{docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/image_refs.py
RENAMED
|
File without changes
|
{docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/lifecycle.py
RENAMED
|
File without changes
|
{docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/matrix.py
RENAMED
|
File without changes
|
{docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/mirror.py
RENAMED
|
File without changes
|
{docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/modes.py
RENAMED
|
File without changes
|
{docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/src/docker_devkit/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_default_targets.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev109}/tests/test_score_generate.py
RENAMED
|
File without changes
|