docker-devkit 0.1.22__tar.gz → 0.1.23.dev36667933546__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.1.23.dev36667933546/.github/workflows/ci-cd.yml +98 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/AGENTS.md +7 -2
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/PKG-INFO +1 -1
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/pyproject.toml +3 -1
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/release-devkit.yaml +1 -1
- docker_devkit-0.1.23.dev36667933546/src/docker_devkit/matrix.py +96 -0
- docker_devkit-0.1.23.dev36667933546/tests/test_matrix.py +129 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/uv.lock +34 -3
- docker_devkit-0.1.22/.github/workflows/ci.yml +0 -29
- docker_devkit-0.1.22/.github/workflows/release.yml +0 -33
- docker_devkit-0.1.22/plan.md +0 -145
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/.gitattributes +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/.gitignore +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/.python-version +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/CLAUDE.md +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/LICENSE +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/NOTICE +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/README.md +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/ruff.base.toml +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/ruff.toml +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/__init__.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/build_docker.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/config.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/context_sha.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/detect_gpu.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/documents.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/down.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/image_refs.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/lifecycle.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/mirror.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/modes.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/py.typed +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/score_generate.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/up.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/tests/test_config.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/tests/test_context_sha.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/tests/test_default_targets.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/tests/test_documents.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/tests/test_image_refs.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/tests/test_lifecycle.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/tests/test_mirror.py +0 -0
- {docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/tests/test_score_generate.py +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
name: CI/CD
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main, dev]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [dev]
|
|
9
|
+
|
|
10
|
+
# Cancel superseded runs on the same branch, except main — where publish-stable
|
|
11
|
+
# must never die mid-release (overlapping release merges queue instead).
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ci-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
env:
|
|
20
|
+
RELEASE_DEVKIT_VERSION: 0.1.25
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
check:
|
|
24
|
+
if: github.ref != 'refs/heads/main'
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v5
|
|
28
|
+
with:
|
|
29
|
+
fetch-depth: 0
|
|
30
|
+
fetch-tags: true
|
|
31
|
+
persist-credentials: false
|
|
32
|
+
- uses: astral-sh/setup-uv@v7
|
|
33
|
+
with:
|
|
34
|
+
enable-cache: true
|
|
35
|
+
- run: uv run preflight-python
|
|
36
|
+
- shell: bash
|
|
37
|
+
run: uvx --from release-devkit==${{ env.RELEASE_DEVKIT_VERSION }} publish-stable --dry-run
|
|
38
|
+
|
|
39
|
+
ensure-release-pr:
|
|
40
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
permissions:
|
|
43
|
+
contents: write
|
|
44
|
+
pull-requests: write
|
|
45
|
+
env:
|
|
46
|
+
GH_TOKEN: ${{ github.token }}
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v5
|
|
49
|
+
with:
|
|
50
|
+
fetch-depth: 0
|
|
51
|
+
fetch-tags: true
|
|
52
|
+
persist-credentials: false
|
|
53
|
+
- uses: astral-sh/setup-uv@v7
|
|
54
|
+
- shell: bash
|
|
55
|
+
run: uvx --from release-devkit==${{ env.RELEASE_DEVKIT_VERSION }} ensure-release-pr
|
|
56
|
+
|
|
57
|
+
publish-stable:
|
|
58
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
environment: release
|
|
61
|
+
permissions:
|
|
62
|
+
contents: write
|
|
63
|
+
id-token: write
|
|
64
|
+
env:
|
|
65
|
+
GH_TOKEN: ${{ github.token }}
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@v5
|
|
68
|
+
with:
|
|
69
|
+
ref: main
|
|
70
|
+
fetch-depth: 0
|
|
71
|
+
fetch-tags: true
|
|
72
|
+
- uses: astral-sh/setup-uv@v7
|
|
73
|
+
- shell: bash
|
|
74
|
+
run: uvx --from release-devkit==${{ env.RELEASE_DEVKIT_VERSION }} publish-stable
|
|
75
|
+
env:
|
|
76
|
+
NPM_CONFIG_LOGLEVEL: verbose
|
|
77
|
+
|
|
78
|
+
publish-dev:
|
|
79
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
|
|
80
|
+
needs: [check]
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
environment: release
|
|
83
|
+
permissions:
|
|
84
|
+
contents: read
|
|
85
|
+
id-token: write
|
|
86
|
+
env:
|
|
87
|
+
GH_TOKEN: ${{ github.token }}
|
|
88
|
+
steps:
|
|
89
|
+
- uses: actions/checkout@v5
|
|
90
|
+
with:
|
|
91
|
+
fetch-depth: 0
|
|
92
|
+
fetch-tags: true
|
|
93
|
+
persist-credentials: false
|
|
94
|
+
- uses: astral-sh/setup-uv@v7
|
|
95
|
+
- shell: bash
|
|
96
|
+
run: uvx --from release-devkit==${{ env.RELEASE_DEVKIT_VERSION }} publish-dev
|
|
97
|
+
env:
|
|
98
|
+
NPM_CONFIG_LOGLEVEL: verbose
|
|
@@ -10,11 +10,15 @@ Config surfaces org-wide follow one law (its home is release-devkit's `AGENTS.md
|
|
|
10
10
|
|
|
11
11
|
## Release flow
|
|
12
12
|
|
|
13
|
-
Publishing rides `
|
|
13
|
+
Publishing rides `ci-cd.yml` via inlined `uvx --from release-devkit==${{ env.RELEASE_DEVKIT_VERSION }}` steps, version-pinned in the workflow `env:` (see release-devkit's `AGENTS.md`), never a project dependency (docker-devkit sits inside its own dependency graph; a project-level release-devkit edge is a resolver cycle), under OIDC trusted publishing (publisher bound to `ci-cd.yml`, `release` environment). The committed `pyproject.toml` version is permanently the `0.0.0.dev0` sentinel; the `docker-devkit-v*` tags are the version ledger (declared `major_minor` line in `release-devkit.yaml`, patch-auto within the line). While pre-1.0, breaking changes ride the normal patch flow without a bump; from 1.0 on, API-breaking changes ship with a manually bumped `major_minor` — patch-auto assumes additive changes.
|
|
14
|
+
|
|
15
|
+
## Mirror consumption
|
|
16
|
+
|
|
17
|
+
Consumers with docker images (placeframe, capture-tool) run `mirror` via the docker-devkit PyPI dependency — inline in their `mirror` job (checkout + `setup-uv` + ghcr login + `uv run mirror`), version-sourced by the consumer's `uv.lock`. The job carries `permissions: { contents: read, packages: write }` + `env: GITHUB_TOKEN` (ghcr login for `crane copy`). Pure-Python devkits have no `mirror` job.
|
|
14
18
|
|
|
15
19
|
## Shape
|
|
16
20
|
|
|
17
|
-
Entry points (`[project.scripts]`): `up` → `up.py:app`, `down` → `down.py:app`, `build` → `build_docker.py:app`, `mirror` → `mirror.py:app`, `generate-score` → `score_generate.py:app`. All accept `--help`.
|
|
21
|
+
Entry points (`[project.scripts]`): `up` → `up.py:app`, `down` → `down.py:app`, `build` → `build_docker.py:app`, `mirror` → `mirror.py:app`, `generate-score` → `score_generate.py:app`, `docker-build-matrix` → `matrix.py:app`. All accept `--help`.
|
|
18
22
|
|
|
19
23
|
| Module | Role |
|
|
20
24
|
|---|---|
|
|
@@ -25,6 +29,7 @@ Entry points (`[project.scripts]`): `up` → `up.py:app`, `down` → `down.py:ap
|
|
|
25
29
|
| `config.py` | `DockerDevkitConfig` — the root `docker-devkit.yaml` schema: a required `requires` PEP 440 specifier plus optional `lifecycle` / `mirror` / `generate-score` sections, `extra="forbid"` on every model. `load_config(root)` is the single strictyaml entry point: parses the file (returns `None` when absent — the no-config → single-file-default path for `up`/`down`), then self-checks the installed docker-devkit version against `requires` (refuses loudly outside range; the `0.0.0.dev0` dev sentinel bypasses the check, not the field). The section models (`LifecycleConfig`, `MirrorConfig`, `ScoreConfig`) live here; `load_lifecycle_config` / `load_mirror_config` / `load_score_config` in their modules are thin wrappers over it. |
|
|
26
30
|
| `lifecycle.py` | `LifecycleConfig` (model in `config.py`) — the `lifecycle:` section declaration (`files`: compose files in layering order with `{gpu}` templating; `dev_file`: optional dev overlay); `load_lifecycle_config()` (reads the `lifecycle:` section via `config.load_config`, `None` when the section or file is absent), `resolve_manifest()` / `require_manifest()` (the raising form) / `resolve_lock()` (the image manifest and its lock are a named pair — `workloads/images.yml` + `workloads/images.lock`, with the legacy root `compose.bake.yml` + `.env.lock` pair still resolved so repos can upgrade before moving files), `enforce_bake_declaration()` (a manifest under either name requires a declaration), `expand_compose_files()` (assembles the `-f` list; a `{gpu}` entry is dropped when the GPU resolves to `none`; `dev_file` appended when a dev overlay applies). |
|
|
27
31
|
| `detect_gpu.py` | `detect_gpu()` → `cuda`/`rocm`/`none` from host devices; the `Gpu` literal. |
|
|
32
|
+
| `matrix.py` | `docker-build-matrix` — emit the CI build matrix as one `matrix=<JSON>` `$GITHUB_OUTPUT` line: one entry per variant (`common`/`cuda`/`rocm`, derived from the service-name suffix convention), `targets` = that variant's explicitly-buildable services with dependents grouped with the base their `additional_contexts: target:` references pull in (one leg builds a whole dependency-closure group in a single bake invocation — the safe default over hoping registry cache makes flat legs suffice). Tagless services never become explicit targets (bake rejects a tagless target under `--push`) — they ride as in-invocation dependencies; `x-cross-compile-targets` are excluded; a group spanning variants is a loud error. |
|
|
28
33
|
| `modes.py` | `resolve_auth_mode()` — validates `PUBLIC_URL` + `AUTH_MODE` from `.env`, rejecting `keycloak` over cleartext `http://`. |
|
|
29
34
|
| `context_sha.py` | `compute_service_shas(repo_root, bake: BakeDocument)` — per-service `tree-<hash>` image tags over the `.dockerignore`-allowlisted git tree. The workload-tree root is derived as the single top-level directory containing every tagged service's Dockerfile (a bake graph spanning several top-level directories is a loud error); files under it that belong to no tagged service are excluded from every SHA rather than shared into all of them. |
|
|
30
35
|
| `documents.py` | `BakeDocument` + `parse_bake(path)` — the typed bake-file model every consumer of bake data goes through (build, up, down, generate-score, the reference collectors). A field validator enforces at parse time that every `x-base-images` value is `path:tag` or `path@digest` (a `${…}` hole or a bare path is a validation error, killing the silent-unpinned `FROM` → `:latest` failure mode). `digest_pinned(reference)` is the shared tail test. |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "docker-devkit"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.23.dev36667933546"
|
|
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 = [
|
|
@@ -19,6 +19,7 @@ dev = [
|
|
|
19
19
|
"basedpyright>=1.39.10",
|
|
20
20
|
"ruff>=0.14.11",
|
|
21
21
|
"pytest>=8.0.0",
|
|
22
|
+
"python-devkit>=0.1.0",
|
|
22
23
|
"types-pyyaml>=6.0.12.20260906",
|
|
23
24
|
"types-strictyaml>=1.7.3.3",
|
|
24
25
|
]
|
|
@@ -33,6 +34,7 @@ down = "docker_devkit.down:app"
|
|
|
33
34
|
build = "docker_devkit.build_docker:app"
|
|
34
35
|
mirror = "docker_devkit.mirror:app"
|
|
35
36
|
generate-score = "docker_devkit.score_generate:app"
|
|
37
|
+
docker-build-matrix = "docker_devkit.matrix:app"
|
|
36
38
|
|
|
37
39
|
[tool.hatch.build.targets.wheel]
|
|
38
40
|
packages = ["src/docker_devkit"]
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from .detect_gpu import GPU_TYPES
|
|
9
|
+
from .documents import BakeDocument, parse_bake
|
|
10
|
+
from .lifecycle import require_manifest
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
|
|
13
|
+
|
|
14
|
+
TARGET_CONTEXT_PREFIX = "target:"
|
|
15
|
+
PLAIN_VARIANT = "common"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.command()
|
|
19
|
+
def matrix_main() -> None:
|
|
20
|
+
bake = parse_bake(require_manifest(Path.cwd()))
|
|
21
|
+
print(f"matrix={json.dumps({'include': compute_matrix_entries(bake)})}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# One entry per variant: targets are the dependency-closure groups of that variant's
|
|
25
|
+
# explicitly-buildable services, coalesced into a single leg — the shape a single
|
|
26
|
+
# gpu-resolved build invocation would produce, with dependents grouped with the base
|
|
27
|
+
# their additional_contexts `target:` references pull in.
|
|
28
|
+
def compute_matrix_entries(bake: BakeDocument) -> list[dict[str, str]]:
|
|
29
|
+
entries_by_variant: dict[str, list[str]] = {}
|
|
30
|
+
for group in dependency_groups(bake):
|
|
31
|
+
explicit = sorted(
|
|
32
|
+
service
|
|
33
|
+
for service in group
|
|
34
|
+
if bake.services[service].build.tags and service not in set(bake.cross_compile_targets)
|
|
35
|
+
)
|
|
36
|
+
if not explicit:
|
|
37
|
+
continue
|
|
38
|
+
variant = group_variant(bake, explicit)
|
|
39
|
+
entries_by_variant.setdefault(variant, []).extend(explicit)
|
|
40
|
+
|
|
41
|
+
return [
|
|
42
|
+
{"targets": " ".join(sorted(entries_by_variant[variant])), "variant": variant}
|
|
43
|
+
for variant in sorted(entries_by_variant)
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def dependency_groups(bake: BakeDocument) -> list[list[str]]:
|
|
48
|
+
parent = {service: service for service in bake.services}
|
|
49
|
+
|
|
50
|
+
for name, service in bake.services.items():
|
|
51
|
+
for context in service.build.additional_contexts.values():
|
|
52
|
+
if not context.startswith(TARGET_CONTEXT_PREFIX):
|
|
53
|
+
continue
|
|
54
|
+
dependency = context.removeprefix(TARGET_CONTEXT_PREFIX)
|
|
55
|
+
if dependency not in bake.services:
|
|
56
|
+
raise RuntimeError(f"{name} references unknown build target {dependency!r} via additional_contexts")
|
|
57
|
+
union(parent, find(parent, name), find(parent, dependency))
|
|
58
|
+
|
|
59
|
+
members: dict[str, list[str]] = {}
|
|
60
|
+
for service in bake.services:
|
|
61
|
+
members.setdefault(find(parent, service), []).append(service)
|
|
62
|
+
return sorted(sorted(group) for group in members.values())
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# Tagless services never become explicit targets (bake rejects a tagless target under
|
|
66
|
+
# --push) — they ride along as in-invocation dependencies of the group that needs them,
|
|
67
|
+
# so the variant is decided by the tagged services alone.
|
|
68
|
+
def group_variant(bake: BakeDocument, explicit: list[str]) -> str:
|
|
69
|
+
variants = {service_variant(service) for service in explicit}
|
|
70
|
+
if len(variants) > 1:
|
|
71
|
+
raise RuntimeError(
|
|
72
|
+
f"dependency group spans gpu variants ({', '.join(sorted(explicit))}) — "
|
|
73
|
+
"additional_contexts must stay within one device family"
|
|
74
|
+
)
|
|
75
|
+
return variants.pop()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def service_variant(service: str) -> str:
|
|
79
|
+
for gpu in GPU_TYPES:
|
|
80
|
+
if service.endswith(f"-{gpu}"):
|
|
81
|
+
return gpu
|
|
82
|
+
return PLAIN_VARIANT
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def find(parent: dict[str, str], service: str) -> str:
|
|
86
|
+
root = service
|
|
87
|
+
while parent[root] != root:
|
|
88
|
+
root = parent[root]
|
|
89
|
+
while parent[service] != service:
|
|
90
|
+
parent[service], service = root, parent[service]
|
|
91
|
+
return root
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def union(parent: dict[str, str], left: str, right: str) -> None:
|
|
95
|
+
if left != right:
|
|
96
|
+
parent[right] = left
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from docker_devkit.documents import BakeDocument
|
|
7
|
+
from docker_devkit.matrix import compute_matrix_entries
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def service(dockerfile: str, *, tags: bool = True, contexts: dict[str, str] | None = None) -> dict[str, object]:
|
|
11
|
+
build: dict[str, object] = {"dockerfile": dockerfile}
|
|
12
|
+
if tags:
|
|
13
|
+
build["tags"] = [f"registry/{dockerfile}:latest"]
|
|
14
|
+
if contexts is not None:
|
|
15
|
+
build["additional_contexts"] = contexts
|
|
16
|
+
return {"build": build}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
TAGGED_BASES = BakeDocument.model_validate({
|
|
20
|
+
"services": {
|
|
21
|
+
"api": service("docker/api/Dockerfile"),
|
|
22
|
+
"postgres": service("docker/postgres/Dockerfile"),
|
|
23
|
+
"neural-networks-base-cuda": service("docker/neural-networks-base/Dockerfile"),
|
|
24
|
+
"reconstructor-cuda": service(
|
|
25
|
+
"docker/reconstructor/Dockerfile", contexts={"neural-networks-base": "target:neural-networks-base-cuda"}
|
|
26
|
+
),
|
|
27
|
+
"localizer-cuda": service(
|
|
28
|
+
"docker/localizer/Dockerfile", contexts={"neural-networks-base": "target:neural-networks-base-cuda"}
|
|
29
|
+
),
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
TAGLESS_BASES = BakeDocument.model_validate({
|
|
34
|
+
"services": {
|
|
35
|
+
"api": service("docker/api/Dockerfile"),
|
|
36
|
+
"neural-networks-base-cuda": service("docker/neural-networks-base/Dockerfile", tags=False),
|
|
37
|
+
"reconstructor-cuda": service(
|
|
38
|
+
"docker/reconstructor/Dockerfile", contexts={"neural-networks-base": "target:neural-networks-base-cuda"}
|
|
39
|
+
),
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_tagged_bases_group_with_their_dependents() -> None:
|
|
45
|
+
entries = compute_matrix_entries(TAGGED_BASES)
|
|
46
|
+
|
|
47
|
+
assert entries == [
|
|
48
|
+
{"targets": "api postgres", "variant": "common"},
|
|
49
|
+
{"targets": "localizer-cuda neural-networks-base-cuda reconstructor-cuda", "variant": "cuda"},
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_tagless_bases_ride_as_dependencies_not_targets() -> None:
|
|
54
|
+
entries = compute_matrix_entries(TAGLESS_BASES)
|
|
55
|
+
|
|
56
|
+
assert entries == [
|
|
57
|
+
{"targets": "api", "variant": "common"},
|
|
58
|
+
{"targets": "reconstructor-cuda", "variant": "cuda"},
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_single_service_repo_emits_matrix_of_one() -> None:
|
|
63
|
+
bake = BakeDocument.model_validate({"services": {"livekit-token": service("workloads/livekit-token/Dockerfile")}})
|
|
64
|
+
|
|
65
|
+
assert compute_matrix_entries(bake) == [{"targets": "livekit-token", "variant": "common"}]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_cross_compile_targets_are_excluded() -> None:
|
|
69
|
+
bake = BakeDocument.model_validate({
|
|
70
|
+
"services": {
|
|
71
|
+
"api": service("docker/api/Dockerfile"),
|
|
72
|
+
"zed-capture": service("docker/zed-capture/Dockerfile"),
|
|
73
|
+
},
|
|
74
|
+
"x-cross-compile-targets": ["zed-capture"],
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
assert compute_matrix_entries(bake) == [{"targets": "api", "variant": "common"}]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_non_target_additional_contexts_do_not_group() -> None:
|
|
81
|
+
bake = BakeDocument.model_validate({
|
|
82
|
+
"services": {
|
|
83
|
+
"api": service("docker/api/Dockerfile", contexts={"shared": "docker/shared"}),
|
|
84
|
+
"postgres": service("docker/postgres/Dockerfile"),
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
assert compute_matrix_entries(bake) == [{"targets": "api postgres", "variant": "common"}]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_group_spanning_variants_fails_loudly() -> None:
|
|
92
|
+
bake = BakeDocument.model_validate({
|
|
93
|
+
"services": {
|
|
94
|
+
"api": service("docker/api/Dockerfile"),
|
|
95
|
+
"reconstructor-cuda": service("docker/reconstructor/Dockerfile", contexts={"api": "target:api"}),
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
with pytest.raises(RuntimeError, match="spans gpu variants"):
|
|
100
|
+
compute_matrix_entries(bake)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_unknown_target_reference_fails_loudly() -> None:
|
|
104
|
+
bake = BakeDocument.model_validate({
|
|
105
|
+
"services": {
|
|
106
|
+
"api": service("docker/api/Dockerfile", contexts={"base": "target:missing-base"}),
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
with pytest.raises(RuntimeError, match="unknown build target"):
|
|
111
|
+
compute_matrix_entries(bake)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_matrix_main_prints_github_output_line(
|
|
115
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
116
|
+
) -> None:
|
|
117
|
+
(tmp_path / "workloads").mkdir()
|
|
118
|
+
(tmp_path / "workloads" / "images.yml").write_text(
|
|
119
|
+
"services:\n livekit-token:\n build:\n dockerfile: workloads/livekit-token/Dockerfile\n"
|
|
120
|
+
" tags:\n - ghcr.io/example/livekit-token:latest\n"
|
|
121
|
+
)
|
|
122
|
+
monkeypatch.chdir(tmp_path)
|
|
123
|
+
|
|
124
|
+
from docker_devkit.matrix import matrix_main
|
|
125
|
+
|
|
126
|
+
matrix_main()
|
|
127
|
+
|
|
128
|
+
payload = json.loads(capsys.readouterr().out.splitlines()[0][len("matrix=") :])
|
|
129
|
+
assert payload == {"include": [{"targets": "livekit-token", "variant": "common"}]}
|
|
@@ -34,11 +34,25 @@ wheels = [
|
|
|
34
34
|
|
|
35
35
|
[[package]]
|
|
36
36
|
name = "bashrun"
|
|
37
|
-
version = "0.1.
|
|
37
|
+
version = "0.1.12"
|
|
38
38
|
source = { registry = "https://pypi.org/simple" }
|
|
39
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
39
|
+
sdist = { url = "https://files.pythonhosted.org/packages/19/2c/742acb113293e712d82c5cfce35a30f344385a7d9852d5844b0bcff4f12f/bashrun-0.1.12.tar.gz", hash = "sha256:d5fd926ec1cb1df6e218a98fe8ad737c24b72bcf740bdfbe2ecf4c77894878a0", size = 32364, upload-time = "2026-09-30T04:02:19.854Z" }
|
|
40
40
|
wheels = [
|
|
41
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
41
|
+
{ url = "https://files.pythonhosted.org/packages/f6/1d/4de203dbe4d711b39f7b440bd141b429864e2a3539faa9b6531eab456195/bashrun-0.1.12-py3-none-any.whl", hash = "sha256:9a188b90eb25249d24556dcdaccfa09ca3efe741406a1c71c7e3cecd170f41b0", size = 7511, upload-time = "2026-09-30T04:02:18.991Z" },
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[[package]]
|
|
45
|
+
name = "ci-devkit"
|
|
46
|
+
version = "0.1.14"
|
|
47
|
+
source = { registry = "https://pypi.org/simple" }
|
|
48
|
+
dependencies = [
|
|
49
|
+
{ name = "bashrun" },
|
|
50
|
+
{ name = "pydantic" },
|
|
51
|
+
{ name = "pydantic-settings" },
|
|
52
|
+
]
|
|
53
|
+
sdist = { url = "https://files.pythonhosted.org/packages/22/25/dc42c65e6147eb64477eae4f410b26d630f03f46c3d3bdcdab374df28081/ci_devkit-0.1.14.tar.gz", hash = "sha256:f8b45fa995bba626edda29c191883224173155a39137da5a1bc67f09ad28d337", size = 51204, upload-time = "2026-09-30T04:06:27.581Z" }
|
|
54
|
+
wheels = [
|
|
55
|
+
{ url = "https://files.pythonhosted.org/packages/4f/08/56cafd065e2d00ad90d450867282d79469bc4c7c9eefa1dc11dcf84e0c96/ci_devkit-0.1.14-py3-none-any.whl", hash = "sha256:64b74d797a040d8d57735eda30dbb59844d0de9c64e53dd24e9769d10bc34e18", size = 25689, upload-time = "2026-09-30T04:06:26.522Z" },
|
|
42
56
|
]
|
|
43
57
|
|
|
44
58
|
[[package]]
|
|
@@ -69,6 +83,7 @@ dependencies = [
|
|
|
69
83
|
dev = [
|
|
70
84
|
{ name = "basedpyright" },
|
|
71
85
|
{ name = "pytest" },
|
|
86
|
+
{ name = "python-devkit" },
|
|
72
87
|
{ name = "ruff" },
|
|
73
88
|
{ name = "types-pyyaml" },
|
|
74
89
|
{ name = "types-strictyaml" },
|
|
@@ -90,6 +105,7 @@ requires-dist = [
|
|
|
90
105
|
dev = [
|
|
91
106
|
{ name = "basedpyright", specifier = ">=1.39.10" },
|
|
92
107
|
{ name = "pytest", specifier = ">=8.0.0" },
|
|
108
|
+
{ name = "python-devkit", specifier = ">=0.1.0" },
|
|
93
109
|
{ name = "ruff", specifier = ">=0.14.11" },
|
|
94
110
|
{ name = "types-pyyaml", specifier = ">=6.0.12.20260906" },
|
|
95
111
|
{ name = "types-strictyaml", specifier = ">=1.7.3.3" },
|
|
@@ -290,6 +306,21 @@ wheels = [
|
|
|
290
306
|
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
|
291
307
|
]
|
|
292
308
|
|
|
309
|
+
[[package]]
|
|
310
|
+
name = "python-devkit"
|
|
311
|
+
version = "0.1.15"
|
|
312
|
+
source = { registry = "https://pypi.org/simple" }
|
|
313
|
+
dependencies = [
|
|
314
|
+
{ name = "bashrun" },
|
|
315
|
+
{ name = "ci-devkit" },
|
|
316
|
+
{ name = "pydantic" },
|
|
317
|
+
{ name = "typer" },
|
|
318
|
+
]
|
|
319
|
+
sdist = { url = "https://files.pythonhosted.org/packages/82/53/c3b37f69c35475b09cdf1c18deb193238d7ab5143ee6bc97e5c22c48fe87/python_devkit-0.1.15.tar.gz", hash = "sha256:ac1ef0f2a4622e9c97763f4e353005c41164e2d6d4b130cdb709e811947f253d", size = 34651, upload-time = "2026-09-30T04:12:18.88Z" }
|
|
320
|
+
wheels = [
|
|
321
|
+
{ url = "https://files.pythonhosted.org/packages/b6/81/da4f76cbfccb5a4d5a58a9f51d3e2b7d40f6dc9edd931d7891b2789adeae/python_devkit-0.1.15-py3-none-any.whl", hash = "sha256:0c13684cc780e3ba9b3e25feed61065b355e8fc7436bee043e7c6adf71fa40e7", size = 14537, upload-time = "2026-09-30T04:12:17.918Z" },
|
|
322
|
+
]
|
|
323
|
+
|
|
293
324
|
[[package]]
|
|
294
325
|
name = "python-dotenv"
|
|
295
326
|
version = "1.2.2"
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [main]
|
|
8
|
-
|
|
9
|
-
concurrency:
|
|
10
|
-
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
11
|
-
cancel-in-progress: true
|
|
12
|
-
|
|
13
|
-
env:
|
|
14
|
-
PYTHON_DEVKIT_VERSION: "0.1.13"
|
|
15
|
-
|
|
16
|
-
jobs:
|
|
17
|
-
check:
|
|
18
|
-
runs-on: ubuntu-latest
|
|
19
|
-
permissions:
|
|
20
|
-
contents: read
|
|
21
|
-
steps:
|
|
22
|
-
- uses: actions/checkout@v5
|
|
23
|
-
|
|
24
|
-
- uses: astral-sh/setup-uv@v7
|
|
25
|
-
with:
|
|
26
|
-
enable-cache: true
|
|
27
|
-
|
|
28
|
-
- name: Preflight
|
|
29
|
-
run: uvx --from python-devkit==${PYTHON_DEVKIT_VERSION} preflight-python
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
name: Release
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
workflow_run:
|
|
5
|
-
workflows: [CI]
|
|
6
|
-
types: [completed]
|
|
7
|
-
branches: [main]
|
|
8
|
-
|
|
9
|
-
concurrency:
|
|
10
|
-
group: release
|
|
11
|
-
cancel-in-progress: false
|
|
12
|
-
|
|
13
|
-
jobs:
|
|
14
|
-
publish:
|
|
15
|
-
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'main'
|
|
16
|
-
runs-on: ubuntu-latest
|
|
17
|
-
permissions:
|
|
18
|
-
contents: write
|
|
19
|
-
id-token: write
|
|
20
|
-
env:
|
|
21
|
-
RELEASE_DEVKIT_VERSION: "0.1.24"
|
|
22
|
-
steps:
|
|
23
|
-
- uses: actions/checkout@v5
|
|
24
|
-
with:
|
|
25
|
-
ref: ${{ github.event.workflow_run.head_sha }}
|
|
26
|
-
|
|
27
|
-
# Workaround: fetch-tags is broken with shallow clones
|
|
28
|
-
- run: git fetch --tags origin
|
|
29
|
-
|
|
30
|
-
- uses: astral-sh/setup-uv@v7
|
|
31
|
-
|
|
32
|
-
- name: Publish
|
|
33
|
-
run: uvx --from release-devkit==${RELEASE_DEVKIT_VERSION} publish-stable --config release-devkit.yaml
|
docker_devkit-0.1.22/plan.md
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
# One home for third-party image declarations; docker-devkit stops parsing compose
|
|
2
|
-
|
|
3
|
-
Status note for pickup: COMPLETE. Shipped: docker-devkit 0.1.15, release-devkit
|
|
4
|
-
0.1.11 (both on PyPI). placeframe `dev`: `f2c94d16` (declarations conversion +
|
|
5
|
-
generate-score port completion), `33ddfb26` (score artifacts), `e0bcd9f8`
|
|
6
|
-
(prose) — preflight green. capture-tool `dev`: `ef7660d` (renames + repin),
|
|
7
|
-
`6025c5e` (prose) — lock round-trip stable, rig render green. All four repos
|
|
8
|
-
need operator pushes. Resolved during execution: the fluent-bit declaration key
|
|
9
|
-
is `SEAWEEDFS_AUDIT_IMAGE` (the plan's `FLUENT_BIT_IMAGE` never matched the
|
|
10
|
-
actual compose hole — holes stayed textually unchanged); stale `DOTNET_*`
|
|
11
|
-
declarations and `PYTHON_BASE`/`ZED_BASE` lock entries were pruned in placeframe
|
|
12
|
-
(zero consumers); `INITIALIZE_S3_IMAGE` (consumed by scripts/sweep_postprocess)
|
|
13
|
-
was promoted to a declaration; the generate-score console-script collision was
|
|
14
|
-
resolved by completing the port (config table in placeframe pyproject,
|
|
15
|
-
duplicate module deleted); capture-tool's bake file is `compose.bake.yml`, not
|
|
16
|
-
the `compose.zed.bake.yml` this plan named. NEXT: make-it-sing's own plan.md
|
|
17
|
-
resumes — flip its committed Stage 3 checkpoint (`6b4120c`) from
|
|
18
|
-
`ARG UV_BASE_DIGEST` + `FROM mirror/…${UV_BASE_DIGEST}` to
|
|
19
|
-
`ARG UV_BASE_IMAGE` + `FROM ${UV_BASE_IMAGE}`, repin docker-devkit >=0.1.15.
|
|
20
|
-
|
|
21
|
-
## Why this initiative exists
|
|
22
|
-
|
|
23
|
-
docker-devkit parses consumer compose files for exactly one purpose: discovering
|
|
24
|
-
`x-image-ref` labels (declarations of externally-pulled images) so `run_build`
|
|
25
|
-
can digest-lock them and `mirror-images` can mirror them. That single need dragged
|
|
26
|
-
in a pile of wrong-shaped machinery:
|
|
27
|
-
|
|
28
|
-
- Compose's YAML dialect tags (`!reset`) require PyYAML constructor registration,
|
|
29
|
-
and PyYAML's constructor API is untyped — a type-checker fight with no clean
|
|
30
|
-
resolution (the only surviving form was a suppression-boundary debate).
|
|
31
|
-
- The include-following logic in `run_build` serves zero org repos (the only
|
|
32
|
-
compose include anywhere is make-it-sing's `oci://` one, which contributes
|
|
33
|
-
nothing and was skipped anyway).
|
|
34
|
-
- Two declaration mechanisms exist for one need: `x-image-ref` labels in compose
|
|
35
|
-
(placeframe: fluent-bit, cloudbeaver, keycloak, ngrok) and `x-base-images` in
|
|
36
|
-
the bake file (placeframe: loki, alloy, grafana, and every Dockerfile base).
|
|
37
|
-
- The `_DIGEST` suffix lock shape (`image: path${X_DIGEST:?err}` in compose,
|
|
38
|
-
lock `X_DIGEST=@sha256:…`) authors the mirror path TWICE — bake declaration +
|
|
39
|
-
compose image line — a drift exposure (path drift fails loud at pull; tags
|
|
40
|
-
cannot drift, but the duplication is authored).
|
|
41
|
-
- The Dockerfile suffix form (`FROM path${X_DIGEST}`) has a silent-unpinned
|
|
42
|
-
failure mode: an empty arg yields a tagless `FROM path`, and Docker silently
|
|
43
|
-
pulls `:latest`.
|
|
44
|
-
|
|
45
|
-
## The design
|
|
46
|
-
|
|
47
|
-
- **Single declaration home**: bake `x-base-images`. Keys use the `_IMAGE`
|
|
48
|
-
convention (`UV_BASE_IMAGE`, `KEYCLOAK_IMAGE`, …). One authored string
|
|
49
|
-
(`path:tag`) per third-party image, anywhere in the org.
|
|
50
|
-
- **run_build single-emits** one lock entry per declaration:
|
|
51
|
-
`NAME=path:tag@sha256:…`. No dual shapes, no suffix entries.
|
|
52
|
-
- **Compose image lines** are pure holes: `image: "${KEYCLOAK_IMAGE:?err}"`.
|
|
53
|
-
- **Dockerfiles** use `ARG NAME` + `FROM ${NAME}`. An empty arg is a hard parse
|
|
54
|
-
error — this fixes the silent-`:latest` foot-gun.
|
|
55
|
-
- **BakeDocument gains a field validator**: every `base_images` value must be
|
|
56
|
-
`path:tag` or `path@digest`. The declarations half of `unpinned_references`
|
|
57
|
-
moves to parse time (enforced on every command run, not a CI-only scan).
|
|
58
|
-
- **`collect_repo_references` splits**: `declared_references(root)` (bake
|
|
59
|
-
declarations only — what mirror-images consumes) and a stray-ref scan
|
|
60
|
-
(Dockerfile `FROM`/`COPY --from` literals + score yaml `image:` lines — what
|
|
61
|
-
`unpinned_references` polices; it survives with that narrow job of catching
|
|
62
|
-
hand-written literal refs that bypass the declaration system).
|
|
63
|
-
- **`version_coupling_violations` is unchanged** — it guards a genuinely
|
|
64
|
-
non-derivable agreement (pyproject `tool.uv.required-version` ↔ image tag; the
|
|
65
|
-
tag carries suffixes like `-python3.13-trixie-slim` that pyproject doesn't
|
|
66
|
-
hold). Two authored truths that must agree is a different problem from one
|
|
67
|
-
truth scattered; the check is correct, keep it.
|
|
68
|
-
|
|
69
|
-
## Changes
|
|
70
|
-
|
|
71
|
-
### docker-devkit (this repo) — release 0.1.15
|
|
72
|
-
|
|
73
|
-
- `image_refs.py`: delete `compose_image_refs`, `ComposeDialectLoader`, both
|
|
74
|
-
`add_constructor` registrations, and the compose branch of
|
|
75
|
-
`collect_repo_references`; implement the `declared_references` / stray-scan
|
|
76
|
-
split. `bake_base_image_refs` and `version_coupling_violations` stay.
|
|
77
|
-
- `documents.py`: add the `base_images` field validator.
|
|
78
|
-
- `build_docker.py`: delete the third-party `x-image-ref` resolution block;
|
|
79
|
-
single-emit lock entries from `x-base-images`; wire all bake consumption
|
|
80
|
-
through `parse_bake`/`BakeDocument` (typed access replaces dict plumbing —
|
|
81
|
-
including `compute_service_shas(repo_root, bake: BakeDocument)` and
|
|
82
|
-
`compute_default_targets(bake: BakeDocument, …)` signatures, with `up.py`,
|
|
83
|
-
`down.py`, `score_generate.py` parsing at their call sites); loud guard:
|
|
84
|
-
`--mode ci` requires `x-registry-cache`.
|
|
85
|
-
- Tests: delete the compose-dialect tests; add validator + split-function tests;
|
|
86
|
-
existing fixtures via `BakeDocument.model_validate`.
|
|
87
|
-
- `types-pyyaml` stays as a dev dependency (it types `parse_bake`'s PyYAML
|
|
88
|
-
surface; with the dialect code gone, zero suppressions are needed anywhere).
|
|
89
|
-
- AGENTS.md: update the `image_refs.py` row and any compose-parsing references
|
|
90
|
-
(separate prose commit from the code).
|
|
91
|
-
- Release via the normal tag-ledger flow; operator push (App token cannot push).
|
|
92
|
-
|
|
93
|
-
### release-devkit — next patch (0.1.11)
|
|
94
|
-
|
|
95
|
-
- `mirror_images.py`: switch from
|
|
96
|
-
`collect_repo_references(Path.cwd(), dockerfile_glob=None)` to
|
|
97
|
-
`declared_references(root)` — same refs, declarations-only source.
|
|
98
|
-
|
|
99
|
-
### placeframe — one PR
|
|
100
|
-
|
|
101
|
-
- `compose.bake.yml`: add declarations for the 4 `x-image-ref` services
|
|
102
|
-
(`CLOUDBEAVER_IMAGE`, `KEYCLOAK_IMAGE`, `FLUENT_BIT_IMAGE`, `NGROK_IMAGE`,
|
|
103
|
-
values = today's label strings); rename every existing `_DIGEST` declaration
|
|
104
|
-
key to the `_IMAGE` convention; `x-base-args` keys follow the renames.
|
|
105
|
-
- `compose.yml`: delete the 4 labels (their image lines are textually unchanged —
|
|
106
|
-
they were already `${X_IMAGE:?err}` holes); flip loki×2 / alloy / grafana from
|
|
107
|
-
`path${X_DIGEST:?err}` to `${X_IMAGE:?err}`; inventory every `${…_IMAGE}` hole
|
|
108
|
-
(at minimum `SEAWEEDFS_AUDIT_IMAGE`) and ensure a declaration exists for each.
|
|
109
|
-
- Dockerfiles: convert every base `FROM` to `ARG NAME` + `FROM ${NAME}`.
|
|
110
|
-
- Version-coupling site registry (consumer preflight): rename `base_image` site
|
|
111
|
-
names to the new declaration keys — locate the registry during execution.
|
|
112
|
-
- Regenerate `.env.lock` (`uv run build --lock-only`); preflight green; repin
|
|
113
|
-
docker-devkit `>=0.1.15`.
|
|
114
|
-
- AGENTS.md mentions of `x-image-ref` updated (separate prose commit).
|
|
115
|
-
|
|
116
|
-
### capture-tool
|
|
117
|
-
|
|
118
|
-
- 3 Dockerfiles + `compose.zed.bake.yml` `x-base-args` key renames; regen
|
|
119
|
-
`.env.lock`; repin docker-devkit.
|
|
120
|
-
|
|
121
|
-
### make-it-sing — NOT this initiative
|
|
122
|
-
|
|
123
|
-
Its own `plan.md` resumes AFTER this one lands (its Stage 3/5 consume the new
|
|
124
|
-
pattern). Its committed Stage 3 checkpoint (`6b4120c`) still carries the suffix
|
|
125
|
-
form (`ARG UV_BASE_DIGEST` + `FROM mirror/…${UV_BASE_DIGEST}`) — flip to
|
|
126
|
-
`ARG UV_BASE_IMAGE` + `FROM ${UV_BASE_IMAGE}` when resuming that plan.
|
|
127
|
-
|
|
128
|
-
## Ordering
|
|
129
|
-
|
|
130
|
-
docker-devkit 0.1.15 → release-devkit 0.1.11 → placeframe PR → capture-tool.
|
|
131
|
-
All releases are operator pushes; the sandbox App token has Contents:read only.
|
|
132
|
-
|
|
133
|
-
## Verification
|
|
134
|
-
|
|
135
|
-
- docker-devkit: pytest + ruff + basedpyright all green (the checkpoint's known
|
|
136
|
-
failures must be gone — their absence is the proof the deletion was complete).
|
|
137
|
-
- placeframe / capture-tool: `uv run build --lock-only` round-trip, preflight
|
|
138
|
-
battery, `docker compose config` render.
|
|
139
|
-
|
|
140
|
-
## Deliberately out of scope
|
|
141
|
-
|
|
142
|
-
- Score yaml scanning changes.
|
|
143
|
-
- Renaming the `x-base-images` key itself (it now covers runtime images too —
|
|
144
|
-
placeframe's loki already made that de facto; note it in a bake-file comment).
|
|
145
|
-
- make-it-sing CI stages (owned by its own plan).
|
|
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
|
|
File without changes
|
{docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/build_docker.py
RENAMED
|
File without changes
|
|
File without changes
|
{docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/context_sha.py
RENAMED
|
File without changes
|
{docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/detect_gpu.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/image_refs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docker_devkit-0.1.22 → docker_devkit-0.1.23.dev36667933546}/src/docker_devkit/score_generate.py
RENAMED
|
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
|
|
File without changes
|