docker-devkit 0.1.23.dev36667933546__tar.gz → 0.1.24__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.24/.github/actions/setup-release-devkit/action.yml +11 -0
- docker_devkit-0.1.24/.github/workflows/integrate.yml +56 -0
- docker_devkit-0.1.24/.github/workflows/merge-gate.yml +41 -0
- docker_devkit-0.1.24/.github/workflows/release.yml +54 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/AGENTS.md +19 -27
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/PKG-INFO +2 -1
- docker_devkit-0.1.24/README.md +3 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/pyproject.toml +2 -1
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/release-devkit.yaml +1 -2
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/build_docker.py +36 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/uv.lock +5 -3
- docker_devkit-0.1.23.dev36667933546/.github/workflows/ci-cd.yml +0 -98
- docker_devkit-0.1.23.dev36667933546/README.md +0 -54
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/.gitattributes +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/.gitignore +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/.python-version +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/CLAUDE.md +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/LICENSE +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/NOTICE +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/ruff.base.toml +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/ruff.toml +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/__init__.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/config.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/context_sha.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/detect_gpu.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/documents.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/down.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/image_refs.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/lifecycle.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/matrix.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/mirror.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/modes.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/py.typed +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/score_generate.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/up.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/tests/test_config.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/tests/test_context_sha.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/tests/test_default_targets.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/tests/test_documents.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/tests/test_image_refs.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/tests/test_lifecycle.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/tests/test_matrix.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/tests/test_mirror.py +0 -0
- {docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/tests/test_score_generate.py +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
name: setup-release-devkit
|
|
2
|
+
description: Install release-devkit at the pinned commit into RUNNER_TEMP
|
|
3
|
+
runs:
|
|
4
|
+
using: composite
|
|
5
|
+
steps:
|
|
6
|
+
- shell: bash
|
|
7
|
+
env:
|
|
8
|
+
RELEASE_DEVKIT_COMMIT: edb355edaf2a2f793ea59eca2d9a5ae80b39f2c2
|
|
9
|
+
run: |
|
|
10
|
+
git clone https://github.com/outernet-foundation/release-devkit.git "$RUNNER_TEMP/release-devkit"
|
|
11
|
+
git -C "$RUNNER_TEMP/release-devkit" checkout "$RELEASE_DEVKIT_COMMIT"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Integrate
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: [dev]
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: integrate-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
lint-workflows:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- &checkout
|
|
20
|
+
uses: actions/checkout@v5
|
|
21
|
+
with:
|
|
22
|
+
ref: ${{ github.event.pull_request.head.sha }}
|
|
23
|
+
persist-credentials: false
|
|
24
|
+
- &uv-restore
|
|
25
|
+
uses: astral-sh/setup-uv@v7
|
|
26
|
+
with:
|
|
27
|
+
enable-cache: true
|
|
28
|
+
save-cache: "false"
|
|
29
|
+
- &setup-release-devkit
|
|
30
|
+
uses: ./.github/actions/setup-release-devkit
|
|
31
|
+
- run: uv run --project "$RUNNER_TEMP/release-devkit" --locked --no-dev lint-workflows
|
|
32
|
+
|
|
33
|
+
preflight:
|
|
34
|
+
needs: [lint-workflows]
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
steps:
|
|
37
|
+
- *checkout
|
|
38
|
+
- uses: astral-sh/setup-uv@v7
|
|
39
|
+
with:
|
|
40
|
+
enable-cache: true
|
|
41
|
+
save-cache: "true"
|
|
42
|
+
- run: uv run preflight-python
|
|
43
|
+
|
|
44
|
+
validate-release-plan:
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v5
|
|
48
|
+
with:
|
|
49
|
+
ref: ${{ github.event.pull_request.head.sha }}
|
|
50
|
+
# Perform a full clone, required for tags
|
|
51
|
+
fetch-depth: 0
|
|
52
|
+
fetch-tags: true
|
|
53
|
+
persist-credentials: false
|
|
54
|
+
- *uv-restore
|
|
55
|
+
- *setup-release-devkit
|
|
56
|
+
- run: uv run --project "$RUNNER_TEMP/release-devkit" --locked --no-dev validate-release-plan
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Merge gate
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [dev]
|
|
6
|
+
types: [labeled]
|
|
7
|
+
workflow_run:
|
|
8
|
+
workflows: [Integrate]
|
|
9
|
+
types: [completed]
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: merge-gate-${{ github.event.pull_request.number || github.event.workflow_run.head_branch }}
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
merge-gate:
|
|
19
|
+
if: github.event.label.name == 'ready-to-merge' || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v5
|
|
23
|
+
with:
|
|
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
|
|
26
|
+
fetch-depth: 0
|
|
27
|
+
persist-credentials: false
|
|
28
|
+
- uses: astral-sh/setup-uv@v7
|
|
29
|
+
with:
|
|
30
|
+
enable-cache: true
|
|
31
|
+
save-cache: "false"
|
|
32
|
+
- uses: ./.github/actions/setup-release-devkit
|
|
33
|
+
- uses: actions/create-github-app-token@v3
|
|
34
|
+
id: mint
|
|
35
|
+
with:
|
|
36
|
+
app-id: ${{ vars.MERGE_BOT_APP_ID }}
|
|
37
|
+
private-key: ${{ secrets.MERGE_BOT_APP_PRIVATE_KEY }}
|
|
38
|
+
- run: uv run --project "$RUNNER_TEMP/release-devkit" --locked --no-dev merge-gate
|
|
39
|
+
env:
|
|
40
|
+
GITHUB_TOKEN: ${{ steps.mint.outputs.token }}
|
|
41
|
+
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.workflow_run.head_sha }}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, dev]
|
|
6
|
+
|
|
7
|
+
concurrency:
|
|
8
|
+
group: release-${{ github.ref }}
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
publish-prerelease:
|
|
15
|
+
if: github.ref == 'refs/heads/dev'
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
id-token: write
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v5
|
|
22
|
+
with:
|
|
23
|
+
# Perform a full clone, required for tags
|
|
24
|
+
fetch-depth: 0
|
|
25
|
+
fetch-tags: true
|
|
26
|
+
persist-credentials: false
|
|
27
|
+
- &uv-restore
|
|
28
|
+
uses: astral-sh/setup-uv@v7
|
|
29
|
+
with:
|
|
30
|
+
enable-cache: true
|
|
31
|
+
save-cache: "false"
|
|
32
|
+
- &setup-release-devkit
|
|
33
|
+
uses: ./.github/actions/setup-release-devkit
|
|
34
|
+
- run: uv run --project "$RUNNER_TEMP/release-devkit" --locked --no-dev publish-prerelease
|
|
35
|
+
|
|
36
|
+
release:
|
|
37
|
+
if: github.ref == 'refs/heads/main'
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
permissions:
|
|
40
|
+
contents: write
|
|
41
|
+
id-token: write
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/checkout@v5
|
|
44
|
+
with:
|
|
45
|
+
# Perform a full clone, required for tags
|
|
46
|
+
fetch-depth: 0
|
|
47
|
+
fetch-tags: true
|
|
48
|
+
# Persist credentials so that the release command can push release tags
|
|
49
|
+
persist-credentials: true
|
|
50
|
+
- *uv-restore
|
|
51
|
+
- *setup-release-devkit
|
|
52
|
+
- run: uv run --project "$RUNNER_TEMP/release-devkit" --locked --no-dev release
|
|
53
|
+
env:
|
|
54
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## What this is
|
|
4
4
|
|
|
5
|
-
`docker-devkit` owns the Docker-stack lifecycle commands — `up`, `down`, `build`, `mirror` — plus the helpers they share (`detect_gpu`, `modes`, `context_sha`).
|
|
5
|
+
`docker-devkit` owns the Docker-stack lifecycle commands — `up`, `down`, `build`, `mirror`, `generate-score`, `docker-build-matrix` — plus the helpers they share (`detect_gpu`, `modes`, `context_sha`). A small, generic package that any repo shipping a compose graph can depend on to get the same bring-up/tear-down / cross-build flow, with the compose assembly declared per consumer in the `lifecycle:` section of `docker-devkit.yaml`. The PyPI identity starts its own version tags at `0.1.0`; the terminal `stack-toolkit` distributions (≤0.1.1) are deprecation signposts pointing here, not this package's history.
|
|
6
6
|
|
|
7
|
-
The package is `docker_devkit` (src-layout under `src/docker_devkit/`); all dependencies resolve from PyPI — `bashrun` (shell-outs), `packaging` (`requires` specifier parsing), `pathspec`, `pydantic`/`pydantic-settings`, `pyyaml` (score-document parsing), `strictyaml` (config loading), `typer` (CLIs) — git-source pins only in scratch branches testing unreleased changes.
|
|
7
|
+
The package is `docker_devkit` (src-layout under `src/docker_devkit/`); all dependencies resolve from PyPI — `bashrun` (shell-outs), `ci-devkit` (builds-shelf push for CI lock artifacts), `packaging` (`requires` specifier parsing), `pathspec`, `pydantic`/`pydantic-settings`, `pyyaml` (score-document parsing), `strictyaml` (config loading), `typer` (CLIs) — git-source pins only in scratch branches testing unreleased changes.
|
|
8
8
|
|
|
9
|
-
Config
|
|
9
|
+
Config follows the org law (home: release-devkit's `AGENTS.md`): this is a root-`docker-devkit.yaml` devkit. The schema (`config.DockerDevkitConfig`, strictyaml-loaded, `extra="forbid"` on every model) carries a required `requires` version stamp plus optional `lifecycle` / `mirror` / `generate-score` sections; `config.load_config` self-checks the installed docker-devkit version against `requires` and refuses loudly outside the range (the `0.0.0.dev0` dev sentinel bypasses the check, not the field).
|
|
10
10
|
|
|
11
11
|
## Release flow
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
release-devkit's `AGENTS.md` owns the three-workflow contract; this repo follows it unchanged. Repo-specific facts: release-devkit is never a project dependency (docker-devkit sits inside its own dependency graph; a project-level edge is a resolver cycle), and from 1.0 on API-breaking changes ship with a manually bumped `major_minor` (pre-1.0, breaking changes ride the patch flow; patch-auto assumes additive changes).
|
|
14
14
|
|
|
15
15
|
## Mirror consumption
|
|
16
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.
|
|
17
|
+
Consumers with docker images (placeframe, capture-tool) run `mirror` via the docker-devkit PyPI dependency — inline in their `mirror-images` 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-images` job.
|
|
18
18
|
|
|
19
19
|
## Shape
|
|
20
20
|
|
|
@@ -36,47 +36,39 @@ Entry points (`[project.scripts]`): `up` → `up.py:app`, `down` → `down.py:ap
|
|
|
36
36
|
| `image_refs.py` | Image-reference collection, split by source. `declared_references(root)` enumerates bake `x-base-images` declarations from `workloads/images.yml` and the legacy `compose*.bake.yml` names (parsed and validated via `BakeDocument`) — the single source consumer mirroring consumes. `stray_references(root, …globs)` scans Dockerfile `FROM`, Dockerfile `COPY --from=` (slash-bearing captures only, so build-stage names are skipped), and yaml `image:` lines — hand-written literal refs that bypass the declaration system — across the `workloads/` and `docker/` Dockerfile trees and the `stack/score/` + `score/` source trees; `unpinned_references(root)` polices exactly those strays (every slash-bearing reference must carry a tag, a digest, or a trailing `${build-arg}` suffix — consumer CIs fail on its result). `bake_base_image_refs(bake: BakeDocument)` adapts declarations to `ImageReference` pairs; `version_coupling_violations(root, couplings)` (each `VersionCoupling` names a pyproject-declared version and the `VersionSite` regexes that must restate it — bake `x-base-images` values or raw file text — so non-derivable couplings like uv `required-version` ↔ uv-base-image tag can't drift silently; the mechanism lives here, the registry in consumer repos' preflight) is unchanged. Remote digest resolution goes through `imagetools inspect`. Compose files are parsed by nothing in this package. |
|
|
37
37
|
| `mirror.py` | The mirror namespace's config, inversion, and verbs. `MirrorConfig` (model in `config.py`, the `mirror:` section, key `prefix`), `load_mirror_config()` (reads the `mirror:` section via `config.load_config`, `None` when the section or file is absent), `is_mirrored`/`upstream_ref` (prefix inversion preserving the `:tag@sha256:…` tail), `reference_exists` (imagetools existence probe honoring docker config creds), `mirror_targets` (declared refs filtered by prefix, sorted), the `mirror` verb (crane install + per-target `crane copy`), and `up`'s fallback helpers (`upstream_fallback`, `write_fallback_overlay`). Constraints in "Mirror namespace and upstream fallback". |
|
|
38
38
|
|
|
39
|
-
##
|
|
39
|
+
## Lifecycle declaration contract
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
`up`/`down` assemble the graph from the `lifecycle:` section of the consumer's root `docker-devkit.yaml` — one code path, declared data. Keys: `files` (compose files in layering order, `{gpu}` templating, a `{gpu}` entry dropped when the GPU resolves to `none`; defaults to empty), `dev_file` (dev overlay, appended by `up --dev` and always by `down` so teardown removes containers from prior dev bring-ups). No section → single-file default (`-f compose.yml`). `--compose-file` does not exist; raw `docker compose` is the pressure valve. `--dev` is opt-in — production-shaped bring-up is the default. The image lock is required unconditionally, no knob; consumers pin stock images via `${…_IMAGE:?err}` holes filled by a committed lock. An image manifest present under either resolved name without a lifecycle declaration is a hard error (declare the section, or the file doesn't belong under that name) — guards are budgeted for silent failures only, and a missing lock already fails loudly. The manifest remains the build/up contract: SHA injection and `--build` acceptance key on the manifest existing, and `build` always reads the resolved manifest. `up`/`down` pass `--project-directory` (the invocation root) explicitly, so compose files living in nested trees (`stack/generated/compose/`, overlays) anchor their relative paths at the repo root instead of the first `-f` file's directory. An empty assembly (`files` empty/omitted, no dev overlay) is a loud `up`/`down` error — the honest state for a builds-only repo (a repo with no repo-local runtime graph omits the section and hits `enforce_bake_declaration`).
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
placeframe's shape lives in placeframe's `docker-devkit.yaml`, not in this package: a `lifecycle` section with `files` = `compose.yml`, `compose.postgres.yml`, `compose.{gpu}.yml` (in layering order) and `dev_file` = `compose.dev.yml`.
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
**Consequences.** placeframe's shape lives in placeframe's `docker-devkit.yaml`, not in this package: a `lifecycle` section with `files` = `compose.yml`, `compose.postgres.yml`, `compose.{gpu}.yml` (in layering order) and `dev_file` = `compose.dev.yml`. A consumer repo with no repo-local runtime graph (a builds-only repo) omits the `lifecycle` section; `up`/`down` on such a repo hits `enforce_bake_declaration` (manifest present, no lifecycle) — the honest signal there is no stack to bring up.
|
|
45
|
+
`--quiet-pull` suppresses compose's per-layer progress (CUDA images carry hundreds of layers) while still surfacing pull totals. `--gpu auto` resolves to `cuda`/`rocm`/`none` from host devices and selects the matching `compose.<gpu>.yml`; override only to reproduce a CI environment or test the CPU-only path.
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
## File and env conventions the commands assume
|
|
50
48
|
|
|
51
49
|
- `.env` (required) — carries `PUBLIC_URL` and `AUTH_MODE`; `modes.py` reads it and rejects `keycloak` over cleartext `http://`.
|
|
52
50
|
- `workloads/images.lock` (required in every repo, checked in) — third-party and base-image pins, written by `build`: one `NAME=path:tag@sha256:…` entry per manifest declaration. `up`/`down` refuse without it; consumers satisfy it with a committed lock filling the `${…_IMAGE:?err}` holes. The legacy name `.env.lock` resolves while the manifest still carries its legacy name; the lock always sits beside its manifest as the resolved pair.
|
|
53
51
|
- `workloads/images.yml` (where a local build graph exists, required) — the image manifest and buildx-bake description; its top-level `x-cross-compile-targets` list (optional) is the set of services excluded from the default target list because they need per-arch treatment, opt-in via `--targets`. The legacy root name `compose.bake.yml` resolves identically. `build` reads exactly one manifest: the first of `workloads/images.yml`, `compose.bake.yml` that exists. `buildx bake` resolves the manifest's relative paths against its invocation cwd, so `build` always invokes from the repo root.
|
|
54
|
-
- the compose layers declared in the `lifecycle:` section of `docker-devkit.yaml
|
|
52
|
+
- the compose layers declared in the `lifecycle:` section of `docker-devkit.yaml`; the GPU one is picked by `detect_gpu()` (or the `--gpu` override).
|
|
55
53
|
- `.dockerignore` (where images are built locally) — allowlist that `context_sha.py` hashes over to compute per-service `tree-<hash>` tags. See "Docker build context" in `AGENTS-SHARED.md` for the allowlist rationale.
|
|
56
54
|
|
|
57
55
|
`build` injects the `tree-<hash>` tags of locally-built images into the environment rather than writing them anywhere; `up`/`down` do the same via `compute_service_shas`. The lock stays a pure third-party pin file — a CI invariant forbids built-image digests in the committed, reviewed lock.
|
|
58
56
|
|
|
59
|
-
|
|
57
|
+
## Mirror namespace and upstream fallback
|
|
60
58
|
|
|
61
|
-
|
|
59
|
+
Third-party images are consumed through an org-owned mirror namespace (`ghcr.io/outernet-foundation/mirror/<upstream-host>/<path>…`) instead of pulling upstream registries directly: manifests at upstream get GC'd, and an upstream yank once broke external cloners — digest pins alone don't save you when the manifest is gone. Durability for strangers beats upstream egress: strangers, CI, and prod always pull the mirror. Populating it is CI's job, single writer — engineers never push to the org namespace, and error text must never teach them to (no "run this populate command" hints).
|
|
62
60
|
|
|
63
|
-
|
|
61
|
+
The mirror is declared, not inferred: the `mirror:` section with `prefix` in the consumer's root `docker-devkit.yaml`. Absent section → all mirror behavior off. Under the section:
|
|
64
62
|
|
|
65
|
-
- `build` resolves lock digests for mirror-prefixed declarations against the upstream reference (prefix stripped, tag/digest tail preserved) and records
|
|
63
|
+
- `build` resolves lock digests for mirror-prefixed declarations against the upstream reference (prefix stripped, tag/digest tail preserved) and records it under the mirror name. Valid because `crane copy` produces byte-identical manifests — upstream and mirror digests are the same digest; a manifest re-wrap (`docker buildx imagetools`-shaped tooling) would change the top-level digest and break lock equality. This makes declare → re-lock walkable before CI has mirrored anything; lock lines are never hand-authored.
|
|
66
64
|
- `up` fails closed: after lock resolution it probes each mirror-prefixed lock entry; a missing mirror image is a hard `RuntimeError`, not a warning — people ignore warnings, and warnings are errors someone was too lazy to fix. The only escape is the per-run `--allow-upstream-fallback` flag. Under the flag, missing entries become digest-preserving address substitutions (same bytes from the upstream host), printed as plain statements — the operator asked for fallback, the run reports what it did. The rewrite is authoritative on both consumption paths: an appended `--env-file` overlay (compose's later env files win) plus `os.environ` before `build`'s bake (shell env wins over env files; `run_build` re-applies the overlay after its own lock update so bake is never clobbered). CI never passes the flag — the mirror job runs before anything needs the images.
|
|
67
|
-
- 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
|
|
68
|
-
|
|
69
|
-
**Consequences.** 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).
|
|
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
|
+
- 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).
|
|
70
67
|
|
|
71
|
-
|
|
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.
|
|
72
69
|
|
|
73
|
-
Lifecycle
|
|
74
|
-
|
|
75
|
-
### `--quiet-pull` logging and GPU auto-detection
|
|
76
|
-
|
|
77
|
-
`--quiet-pull` suppresses compose's per-layer progress (CUDA images carry hundreds of layers) while still surfacing pull totals. `--gpu auto` resolves to `cuda`/`rocm`/`none` from host devices and selects the matching `compose.<gpu>.yml`; override only to reproduce a CI environment or test the CPU-only path.
|
|
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`.
|
|
78
71
|
|
|
79
72
|
## See also
|
|
80
73
|
|
|
81
|
-
- `README.md` — human-facing setup and usage.
|
|
82
74
|
- [`bashrun`](https://github.com/outernet-foundation/bashrun) — the guardrailed subprocess wrapper the lifecycle commands shell out through.
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: docker-devkit
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.24
|
|
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
10
|
Requires-Dist: packaging>=24.1
|
|
10
11
|
Requires-Dist: pathspec>=1.0.4
|
|
11
12
|
Requires-Dist: pydantic-settings>=2.9.1
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "docker-devkit"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.24"
|
|
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
9
|
"packaging>=24.1",
|
|
9
10
|
"pathspec>=1.0.4",
|
|
10
11
|
"pydantic>=2.11.7",
|
{docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/build_docker.py
RENAMED
|
@@ -10,6 +10,7 @@ from typing import Annotated, Any, Literal
|
|
|
10
10
|
|
|
11
11
|
import typer
|
|
12
12
|
from bashrun.bash import bash, bash_output
|
|
13
|
+
from ci_devkit.builds import push_build
|
|
13
14
|
from .detect_gpu import GPU_TYPES, Gpu, detect_gpu
|
|
14
15
|
from .documents import BakeDocument, digest_pinned, parse_bake
|
|
15
16
|
from pydantic_settings import BaseSettings
|
|
@@ -31,6 +32,7 @@ METADATA_PATH = Path("metadata.json")
|
|
|
31
32
|
|
|
32
33
|
Mode = Literal["local", "ci"]
|
|
33
34
|
|
|
35
|
+
IMAGES_LOCK_PLATFORM = "images-lock"
|
|
34
36
|
|
|
35
37
|
app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
|
|
36
38
|
|
|
@@ -51,6 +53,17 @@ def build(
|
|
|
51
53
|
list[str] | None,
|
|
52
54
|
typer.Option("--targets", "-t", help="Build only these services (from the image manifest)."),
|
|
53
55
|
] = 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,
|
|
54
67
|
) -> None:
|
|
55
68
|
run_build(
|
|
56
69
|
upgrade=upgrade,
|
|
@@ -60,6 +73,9 @@ def build(
|
|
|
60
73
|
gpu_only=gpu_only,
|
|
61
74
|
no_cache=no_cache,
|
|
62
75
|
targets_opt=targets_opt,
|
|
76
|
+
builds_registry=builds_registry,
|
|
77
|
+
lock_project=lock_project,
|
|
78
|
+
run_number=run_number,
|
|
63
79
|
)
|
|
64
80
|
|
|
65
81
|
|
|
@@ -73,6 +89,9 @@ def run_build(
|
|
|
73
89
|
no_cache: bool = False,
|
|
74
90
|
targets_opt: list[str] | None = None,
|
|
75
91
|
env_overlay: dict[str, str] | None = None,
|
|
92
|
+
builds_registry: str | None = None,
|
|
93
|
+
lock_project: str | None = None,
|
|
94
|
+
run_number: int = 0,
|
|
76
95
|
) -> None:
|
|
77
96
|
root = Path.cwd()
|
|
78
97
|
manifest = require_manifest(root)
|
|
@@ -204,6 +223,23 @@ def run_build(
|
|
|
204
223
|
if not set(targets) <= baked_images.keys():
|
|
205
224
|
raise RuntimeError("Bake output does not cover the requested targets")
|
|
206
225
|
|
|
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
|
+
)
|
|
242
|
+
|
|
207
243
|
|
|
208
244
|
# Vibe code - Gemini 3
|
|
209
245
|
def _check_gc_limits(min_gb: int = 60):
|
|
@@ -43,16 +43,16 @@ wheels = [
|
|
|
43
43
|
|
|
44
44
|
[[package]]
|
|
45
45
|
name = "ci-devkit"
|
|
46
|
-
version = "0.1.
|
|
46
|
+
version = "0.1.15.dev36756330648"
|
|
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/5c/1e/fdad4f4fd539491f8ddeaf45e349801b274e9f7a8b00f768c6e83df1b97d/ci_devkit-0.1.15.dev36756330648.tar.gz", hash = "sha256:55b497b377a5e10e85c773b57897e4d1f883129b45567d1ecb581c02a18eabe4", size = 53675, upload-time = "2026-09-30T18:10:54.868Z" }
|
|
54
54
|
wheels = [
|
|
55
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
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" },
|
|
56
56
|
]
|
|
57
57
|
|
|
58
58
|
[[package]]
|
|
@@ -70,6 +70,7 @@ version = "0.0.0.dev0"
|
|
|
70
70
|
source = { editable = "." }
|
|
71
71
|
dependencies = [
|
|
72
72
|
{ name = "bashrun" },
|
|
73
|
+
{ name = "ci-devkit" },
|
|
73
74
|
{ name = "packaging" },
|
|
74
75
|
{ name = "pathspec" },
|
|
75
76
|
{ name = "pydantic" },
|
|
@@ -92,6 +93,7 @@ dev = [
|
|
|
92
93
|
[package.metadata]
|
|
93
94
|
requires-dist = [
|
|
94
95
|
{ name = "bashrun", specifier = ">=0.1.0" },
|
|
96
|
+
{ name = "ci-devkit", specifier = "==0.1.15.dev36756330648" },
|
|
95
97
|
{ name = "packaging", specifier = ">=24.1" },
|
|
96
98
|
{ name = "pathspec", specifier = ">=1.0.4" },
|
|
97
99
|
{ name = "pydantic", specifier = ">=2.11.7" },
|
|
@@ -1,98 +0,0 @@
|
|
|
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
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# docker-devkit
|
|
2
|
-
|
|
3
|
-
Docker-stack lifecycle commands — `up`, `down`, `build` — with the compose assembly declared per repo in the `lifecycle:` section of `docker-devkit.yaml`. A repo that authors its own stack (Dockerfiles + `workloads/images.yml`) declares its file layers and gets multi-file assembly, per-service SHA injection, and `workloads/images.lock` resolution; a consumer repo that OCI-includes an already-baked upstream stack omits the `lifecycle` section (single-file default) and runs the same commands as a thin wrapper over its own `.env` + committed lock.
|
|
4
|
-
|
|
5
|
-
See [`AGENTS.md`](./AGENTS.md) for the file/layout conventions the commands assume (`workloads/images.yml` + `workloads/images.lock` — with the legacy root `compose.bake.yml` + `.env.lock` pair still resolved — and `PUBLIC_URL` / `AUTH_MODE` in `.env`) and the declaration contract.
|
|
6
|
-
|
|
7
|
-
## Setup
|
|
8
|
-
|
|
9
|
-
Requires Python 3.13+ and [uv](https://docs.astral.sh/uv/).
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
uv sync
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Usage
|
|
16
|
-
|
|
17
|
-
From a stack repo's root:
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
uv run up # docker compose up (auto-detects GPU; assembles the declared lifecycle files)
|
|
21
|
-
uv run up --dev # layer the declared dev overlay (bind-mount/debug bring-up)
|
|
22
|
-
uv run up --build # build images locally first (requires workloads/images.yml)
|
|
23
|
-
uv run up --gpu none # override GPU auto-detection
|
|
24
|
-
uv run up --allow-upstream-fallback # per-run escape: pull unmirrored lock entries from upstream at the same digest
|
|
25
|
-
uv run down # docker compose down
|
|
26
|
-
uv run down -v # also remove named volumes
|
|
27
|
-
uv run build # cross-build all images per workloads/images.yml
|
|
28
|
-
uv run build --lock-only # refresh the image lock without building
|
|
29
|
-
uv run mirror # populate the org mirror namespace from mirror-prefixed declarations (CI)
|
|
30
|
-
uv run generate-score # regenerate Score compose/k8s manifests per the generate-score: section of docker-devkit.yaml
|
|
31
|
-
uv run generate-score --local # local-cluster variant (local storage class, gitignored output)
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Repos that consume third-party images through the org mirror namespace declare a `mirror:` section in `docker-devkit.yaml` (`prefix: ghcr.io/outernet-foundation/mirror`); `up` then fails closed on any lock entry not mirrored yet, and `build` resolves lock digests against upstream. See the "Mirror namespace and upstream fallback" section in [`AGENTS.md`](./AGENTS.md).
|
|
35
|
-
|
|
36
|
-
## Consuming from another repo
|
|
37
|
-
|
|
38
|
-
Install from PyPI and use its entry points:
|
|
39
|
-
|
|
40
|
-
```toml
|
|
41
|
-
[project]
|
|
42
|
-
dependencies = ["docker-devkit>=0.1.0"]
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
`bashrun` resolves transitively from PyPI. To test an unreleased change, pin the repo at a git ref in a scratch branch instead (`docker-devkit = { git = "…", rev = "<sha>" }` under `[tool.uv.sources]`) and drop the pin when the release lands.
|
|
46
|
-
|
|
47
|
-
## Development
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
uv run ruff check .
|
|
51
|
-
uv run ruff format --check .
|
|
52
|
-
uv run basedpyright
|
|
53
|
-
uv run pytest
|
|
54
|
-
```
|
|
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.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/context_sha.py
RENAMED
|
File without changes
|
{docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/detect_gpu.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/src/docker_devkit/image_refs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docker_devkit-0.1.23.dev36667933546 → docker_devkit-0.1.24}/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
|
|
File without changes
|