diff --git a/.github/workflows/py-image-builder.yaml b/.github/workflows/pymage.yaml similarity index 84% rename from .github/workflows/py-image-builder.yaml rename to .github/workflows/pymage.yaml index ae605c6..dac67f6 100644 --- a/.github/workflows/py-image-builder.yaml +++ b/.github/workflows/pymage.yaml @@ -1,10 +1,10 @@ -name: py-image-builder +name: pymage on: pull_request: paths: - - "py-image-builder/**" - - ".github/workflows/py-image-builder.yaml" + - "pymage/**" + - ".github/workflows/pymage.yaml" jobs: ci: @@ -26,29 +26,29 @@ jobs: - uses: actions/setup-go@v6.2.0 with: - go-version-file: py-image-builder/go.mod - cache-dependency-path: py-image-builder/go.sum + go-version-file: pymage/go.mod + cache-dependency-path: pymage/go.sum - uses: actions/setup-python@v6.2.0 with: python-version: "3.12" - name: Run tests - working-directory: py-image-builder + working-directory: pymage run: go test -race ./... - name: Run golangci-lint uses: golangci/golangci-lint-action@v9.2.0 with: - working-directory: py-image-builder + working-directory: pymage version: latest - name: Build the CLI - working-directory: py-image-builder - run: go build -o pib . + working-directory: pymage + run: go build -o pymage . - name: Prepare a demo app, wheelhouse, and hashed lock - working-directory: py-image-builder + working-directory: pymage run: | set -euo pipefail mkdir -p demo/wheelhouse demo/src @@ -57,7 +57,7 @@ jobs: import click import six - print("py-image-builder demo OK | six=%s click=%s" % (six.__version__, click.__version__)) + print("pymage demo OK | six=%s click=%s" % (six.__version__, click.__version__)) PY python -m pip install --upgrade pip @@ -75,10 +75,10 @@ jobs: cat demo/requirements.txt - name: Build and push the image (no Docker daemon involved) - working-directory: py-image-builder + working-directory: pymage run: | set -euo pipefail - ./pib build \ + ./pymage build \ --base "$BASE" \ --lock demo/requirements.txt \ --find-links demo/wheelhouse \ @@ -92,14 +92,14 @@ jobs: cat demo/sbom.json - name: Verify the build is reproducible (same digest twice) - working-directory: py-image-builder + working-directory: pymage run: | set -euo pipefail common=(--base "$BASE" --lock demo/requirements.txt --find-links demo/wheelhouse \ --source demo/src --python "$PYTHON" \ --entrypoint python --entrypoint /app/app.py --insecure --push=false) - d1=$(./pib build "${common[@]}" --print-digest) - d2=$(./pib build "${common[@]}" --print-digest) + d1=$(./pymage build "${common[@]}" --print-digest) + d2=$(./pymage build "${common[@]}" --print-digest) echo "digest #1: $d1" echo "digest #2: $d2" if [ "$d1" != "$d2" ]; then @@ -114,13 +114,13 @@ jobs: docker pull "$REGISTRY/demo:v1" out=$(docker run --rm "$REGISTRY/demo:v1") echo "container output: $out" - echo "$out" | grep -q "py-image-builder demo OK" + echo "$out" | grep -q "pymage demo OK" - name: Install crane (for manifest inspection) run: go install github.com/google/go-containerregistry/cmd/crane@v0.19.2 - name: Adding a dependency reuses existing layers (only one new layer) - working-directory: py-image-builder + working-directory: pymage run: | set -euo pipefail # Resolve one more pure-python wheel and append it to the lock. @@ -129,7 +129,7 @@ jobs: echo "idna==3.7 --hash=sha256:$(sha256sum "$idna_whl" | awk '{print $1}')" >> demo/requirements.txt # Build the new image (now with the extra dependency) and push it. - ./pib build \ + ./pymage build \ --base "$BASE" \ --lock demo/requirements.txt \ --find-links demo/wheelhouse \ @@ -143,7 +143,7 @@ jobs: docker pull "$REGISTRY/demo:v2" out=$(docker run --rm "$REGISTRY/demo:v2") echo "container output: $out" - echo "$out" | grep -q "py-image-builder demo OK" + echo "$out" | grep -q "pymage demo OK" # Compare layer sets: every layer from v1 must still be present in v2 # (unchanged digests), and v2 must have exactly one more layer. diff --git a/py-image-builder/DESIGN.md b/pymage/DESIGN.md similarity index 98% rename from py-image-builder/DESIGN.md rename to pymage/DESIGN.md index a11b311..47e03cc 100644 --- a/py-image-builder/DESIGN.md +++ b/pymage/DESIGN.md @@ -1,4 +1,4 @@ -# `py-image-builder` — a docker-less, layer-aware Python image builder +# `pymage` — a docker-less, layer-aware Python image builder > Status: **Design / plan** (no code yet). This document proposes the architecture > and an incremental implementation plan. Comments and pushback welcome. @@ -226,7 +226,7 @@ pure-python layers), and publish an **image index** referencing each arch manife - **A single CLI**, like `ko` — no daemon, no server, no infra. It builds and pushes (or saves) an image in one command: ``` - py-image-builder build \ + pymage build \ --base cgr.dev/chainguard/python@sha256:... \ --lock requirements.txt \ --source ./ \ @@ -238,7 +238,7 @@ pure-python layers), and publish an **image index** referencing each arch manife `--push=false` (build to local OCI layout / `--tarball`), `--print-digest` (compute the would-be digest offline, no push), and standard ggcr keychain auth (Docker config / cloud helpers) so it works in CI without extra setup. -- New self-contained module dir `py-image-builder/` with its own `go.mod` and +- New self-contained module dir `pymage/` with its own `go.mod` and `README.md`, mirroring repo conventions. **No `main.tf` / Cloud Run** — this is a standalone CLI, not a hosted service. @@ -246,7 +246,7 @@ pure-python layers), and publish an **image index** referencing each arch manife Each phase is independently useful and reviewable. -1. **Skeleton + deterministic tar.** New `py-image-builder/` Go module; a function +1. **Skeleton + deterministic tar.** New `pymage/` Go module; a function that writes a normalized, reproducible tar and a round-trip determinism test (same input ⇒ same bytes ⇒ same digest). 2. **Wheel → layer.** Parse a wheel zip, lay out files into the fixed prefix, diff --git a/py-image-builder/README.md b/pymage/README.md similarity index 98% rename from py-image-builder/README.md rename to pymage/README.md index 4bcd1dc..7ff4c0e 100644 --- a/py-image-builder/README.md +++ b/pymage/README.md @@ -1,4 +1,4 @@ -# `py-image-builder` +# `pymage` A **docker-less**, layer-aware container image builder for Python applications, in the spirit of [`ko`](https://github.com/ko-build/ko). It builds and pushes @@ -23,7 +23,7 @@ See [`DESIGN.md`](./DESIGN.md) for the full rationale. ## Usage ``` -py-image-builder build \ +pymage build \ --base cgr.dev/chainguard/python:latest \ --lock requirements.txt \ # pinned + hashed (pip-compile / uv pip compile --generate-hashes) --find-links ./wheelhouse \ # directory of resolved .whl files diff --git a/py-image-builder/e2e/e2e_test.go b/pymage/e2e/e2e_test.go similarity index 96% rename from py-image-builder/e2e/e2e_test.go rename to pymage/e2e/e2e_test.go index 68e48e6..78f4feb 100644 --- a/py-image-builder/e2e/e2e_test.go +++ b/pymage/e2e/e2e_test.go @@ -1,4 +1,4 @@ -// Package e2e exercises py-image-builder end to end against a real local OCI +// Package e2e exercises pymage end to end against a real local OCI // registry (go-containerregistry's in-process registry served over HTTP — a // genuine registry daemon, just in the test process). No Docker is required. // @@ -30,10 +30,10 @@ import ( "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/remote" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/build" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/testwheel" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/wheel" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/wheelhouse" + "github.com/imjasonh/terraform-playground/pymage/internal/build" + "github.com/imjasonh/terraform-playground/pymage/internal/testwheel" + "github.com/imjasonh/terraform-playground/pymage/internal/wheel" + "github.com/imjasonh/terraform-playground/pymage/internal/wheelhouse" ) // uploadCounter records the set of blob digests that were actually uploaded diff --git a/py-image-builder/go.mod b/pymage/go.mod similarity index 93% rename from py-image-builder/go.mod rename to pymage/go.mod index 6559a01..f880dea 100644 --- a/py-image-builder/go.mod +++ b/pymage/go.mod @@ -1,4 +1,4 @@ -module github.com/imjasonh/terraform-playground/py-image-builder +module github.com/imjasonh/terraform-playground/pymage go 1.22.2 diff --git a/py-image-builder/go.sum b/pymage/go.sum similarity index 100% rename from py-image-builder/go.sum rename to pymage/go.sum diff --git a/py-image-builder/internal/build/build.go b/pymage/internal/build/build.go similarity index 97% rename from py-image-builder/internal/build/build.go rename to pymage/internal/build/build.go index 17b4d52..997fb5e 100644 --- a/py-image-builder/internal/build/build.go +++ b/pymage/internal/build/build.go @@ -20,9 +20,9 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/mutate" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/ptar" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/wheel" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/wheelhouse" + "github.com/imjasonh/terraform-playground/pymage/internal/ptar" + "github.com/imjasonh/terraform-playground/pymage/internal/wheel" + "github.com/imjasonh/terraform-playground/pymage/internal/wheelhouse" ) // LayerStrategy controls how dependency layers are partitioned. @@ -362,7 +362,7 @@ func mergeEnv(base []string, layout wheel.Layout, extra []string) []string { func history(createdBy string) v1.History { return v1.History{ Created: v1.Time{Time: epoch()}, - CreatedBy: "py-image-builder: " + createdBy, + CreatedBy: "pymage: " + createdBy, } } diff --git a/py-image-builder/internal/build/build_test.go b/pymage/internal/build/build_test.go similarity index 95% rename from py-image-builder/internal/build/build_test.go rename to pymage/internal/build/build_test.go index 2a2005b..e6e73ad 100644 --- a/py-image-builder/internal/build/build_test.go +++ b/pymage/internal/build/build_test.go @@ -11,9 +11,9 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/testwheel" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/wheel" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/wheelhouse" + "github.com/imjasonh/terraform-playground/pymage/internal/testwheel" + "github.com/imjasonh/terraform-playground/pymage/internal/wheel" + "github.com/imjasonh/terraform-playground/pymage/internal/wheelhouse" ) func layerPaths(t *testing.T, l v1.Layer) map[string]bool { diff --git a/py-image-builder/internal/build/resolve.go b/pymage/internal/build/resolve.go similarity index 100% rename from py-image-builder/internal/build/resolve.go rename to pymage/internal/build/resolve.go diff --git a/py-image-builder/internal/cli/cli.go b/pymage/internal/cli/cli.go similarity index 92% rename from py-image-builder/internal/cli/cli.go rename to pymage/internal/cli/cli.go index 146cf45..8fdd54a 100644 --- a/py-image-builder/internal/cli/cli.go +++ b/pymage/internal/cli/cli.go @@ -1,4 +1,4 @@ -// Package cli implements the py-image-builder command line. +// Package cli implements the pymage command line. package cli import ( @@ -15,17 +15,17 @@ import ( "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/spf13/cobra" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/build" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/lock" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/sbom" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/wheel" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/wheelhouse" + "github.com/imjasonh/terraform-playground/pymage/internal/build" + "github.com/imjasonh/terraform-playground/pymage/internal/lock" + "github.com/imjasonh/terraform-playground/pymage/internal/sbom" + "github.com/imjasonh/terraform-playground/pymage/internal/wheel" + "github.com/imjasonh/terraform-playground/pymage/internal/wheelhouse" ) // Root returns the root cobra command. func Root() *cobra.Command { root := &cobra.Command{ - Use: "py-image-builder", + Use: "pymage", Short: "Docker-less, layer-aware Python container image builder", SilenceUsage: true, SilenceErrors: true, diff --git a/py-image-builder/internal/cli/cli_test.go b/pymage/internal/cli/cli_test.go similarity index 97% rename from py-image-builder/internal/cli/cli_test.go rename to pymage/internal/cli/cli_test.go index b783d19..8cd569d 100644 --- a/py-image-builder/internal/cli/cli_test.go +++ b/pymage/internal/cli/cli_test.go @@ -14,7 +14,7 @@ import ( "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/remote" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/testwheel" + "github.com/imjasonh/terraform-playground/pymage/internal/testwheel" ) // TestBuildCommandEndToEnd drives the cobra `build` command exactly as a user diff --git a/py-image-builder/internal/lock/lock.go b/pymage/internal/lock/lock.go similarity index 100% rename from py-image-builder/internal/lock/lock.go rename to pymage/internal/lock/lock.go diff --git a/py-image-builder/internal/lock/lock_test.go b/pymage/internal/lock/lock_test.go similarity index 100% rename from py-image-builder/internal/lock/lock_test.go rename to pymage/internal/lock/lock_test.go diff --git a/py-image-builder/internal/ptar/ptar.go b/pymage/internal/ptar/ptar.go similarity index 98% rename from py-image-builder/internal/ptar/ptar.go rename to pymage/internal/ptar/ptar.go index 1ece255..a89198a 100644 --- a/py-image-builder/internal/ptar/ptar.go +++ b/pymage/internal/ptar/ptar.go @@ -1,7 +1,7 @@ // Package ptar builds deterministic (reproducible) tar archives and turns them // into OCI layers. // -// Reproducibility is the load-bearing property of py-image-builder: identical +// Reproducibility is the load-bearing property of pymage: identical // inputs must yield byte-identical layers so that their content-addressed // digests match and registries can dedupe/skip/mount them. To guarantee that, // every tar we emit: diff --git a/py-image-builder/internal/ptar/ptar_test.go b/pymage/internal/ptar/ptar_test.go similarity index 100% rename from py-image-builder/internal/ptar/ptar_test.go rename to pymage/internal/ptar/ptar_test.go diff --git a/py-image-builder/internal/sbom/sbom.go b/pymage/internal/sbom/sbom.go similarity index 94% rename from py-image-builder/internal/sbom/sbom.go rename to pymage/internal/sbom/sbom.go index 371b3ad..0f531e7 100644 --- a/py-image-builder/internal/sbom/sbom.go +++ b/pymage/internal/sbom/sbom.go @@ -8,7 +8,7 @@ import ( "encoding/json" "sort" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/wheelhouse" + "github.com/imjasonh/terraform-playground/pymage/internal/wheelhouse" ) type doc struct { diff --git a/py-image-builder/internal/sbom/sbom_test.go b/pymage/internal/sbom/sbom_test.go similarity index 89% rename from py-image-builder/internal/sbom/sbom_test.go rename to pymage/internal/sbom/sbom_test.go index 5de99b1..219b56a 100644 --- a/py-image-builder/internal/sbom/sbom_test.go +++ b/pymage/internal/sbom/sbom_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/wheelhouse" + "github.com/imjasonh/terraform-playground/pymage/internal/wheelhouse" ) func TestGenerateDeterministicAndSorted(t *testing.T) { diff --git a/py-image-builder/internal/testwheel/testwheel.go b/pymage/internal/testwheel/testwheel.go similarity index 100% rename from py-image-builder/internal/testwheel/testwheel.go rename to pymage/internal/testwheel/testwheel.go diff --git a/py-image-builder/internal/wheel/wheel.go b/pymage/internal/wheel/wheel.go similarity index 98% rename from py-image-builder/internal/wheel/wheel.go rename to pymage/internal/wheel/wheel.go index 4b990fe..6fb64c4 100644 --- a/py-image-builder/internal/wheel/wheel.go +++ b/pymage/internal/wheel/wheel.go @@ -21,7 +21,7 @@ import ( "sort" "strings" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/ptar" + "github.com/imjasonh/terraform-playground/pymage/internal/ptar" ) // Layout describes where files are installed inside the image filesystem. @@ -181,7 +181,7 @@ func (w *Wheel) consoleScripts(bin string) ([]ptar.File, error) { for _, n := range names { module, attr := eps[n].module, eps[n].attr script := fmt.Sprintf(`#!/usr/bin/env python -# Generated by py-image-builder. +# Generated by pymage. import sys from %s import %s if __name__ == "__main__": diff --git a/py-image-builder/internal/wheel/wheel_test.go b/pymage/internal/wheel/wheel_test.go similarity index 94% rename from py-image-builder/internal/wheel/wheel_test.go rename to pymage/internal/wheel/wheel_test.go index 55beab5..3a1f498 100644 --- a/py-image-builder/internal/wheel/wheel_test.go +++ b/pymage/internal/wheel/wheel_test.go @@ -4,8 +4,8 @@ import ( "strings" "testing" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/ptar" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/testwheel" + "github.com/imjasonh/terraform-playground/pymage/internal/ptar" + "github.com/imjasonh/terraform-playground/pymage/internal/testwheel" ) var layout = Layout{Prefix: "/app/.venv", PythonTag: "python3.12"} diff --git a/py-image-builder/internal/wheelhouse/wheelhouse.go b/pymage/internal/wheelhouse/wheelhouse.go similarity index 95% rename from py-image-builder/internal/wheelhouse/wheelhouse.go rename to pymage/internal/wheelhouse/wheelhouse.go index dfb2aa5..ef10d65 100644 --- a/py-image-builder/internal/wheelhouse/wheelhouse.go +++ b/pymage/internal/wheelhouse/wheelhouse.go @@ -17,8 +17,8 @@ import ( "sort" "strings" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/lock" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/wheel" + "github.com/imjasonh/terraform-playground/pymage/internal/lock" + "github.com/imjasonh/terraform-playground/pymage/internal/wheel" ) // ResolvedWheel pairs a pinned requirement with the wheel file that satisfies diff --git a/py-image-builder/internal/wheelhouse/wheelhouse_test.go b/pymage/internal/wheelhouse/wheelhouse_test.go similarity index 90% rename from py-image-builder/internal/wheelhouse/wheelhouse_test.go rename to pymage/internal/wheelhouse/wheelhouse_test.go index 7ef7388..4fd51a4 100644 --- a/py-image-builder/internal/wheelhouse/wheelhouse_test.go +++ b/pymage/internal/wheelhouse/wheelhouse_test.go @@ -3,8 +3,8 @@ package wheelhouse import ( "testing" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/lock" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/testwheel" + "github.com/imjasonh/terraform-playground/pymage/internal/lock" + "github.com/imjasonh/terraform-playground/pymage/internal/testwheel" ) func TestResolveSortedAndVerified(t *testing.T) { diff --git a/py-image-builder/main.go b/pymage/main.go similarity index 74% rename from py-image-builder/main.go rename to pymage/main.go index f652755..fc059e9 100644 --- a/py-image-builder/main.go +++ b/pymage/main.go @@ -1,4 +1,4 @@ -// Command py-image-builder builds and pushes OCI images for Python applications +// Command pymage builds and pushes OCI images for Python applications // without a Docker daemon, in the spirit of ko. // // It installs each resolved wheel into its own deterministic, content-addressed @@ -10,7 +10,7 @@ import ( "fmt" "os" - "github.com/imjasonh/terraform-playground/py-image-builder/internal/cli" + "github.com/imjasonh/terraform-playground/pymage/internal/cli" ) func main() {