From a9fe0f080dd4576f718c713cf7826c6cb20bc397 Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Sun, 27 Sep 2026 12:56:23 -0400 Subject: [PATCH] fix: build on the multi-arch python companion, pinned by its index digest The default runtime image was docker.io/codeflydev/python:0.0.1, a single linux/amd64 manifest. A recipe rendered for linux/amd64,linux/arm64 pulled the amd64 image for both platforms, so the arm64 half ran amd64 binaries under QEMU and died on its first RUN ("Invalid ELF image for this architecture"). The default is now ghcr.io/codefly-dev/python:0.0.5, the companion of the core this agent links, pinned by the digest of its amd64+arm64 image index. A test holds the tag to core's companion version so a core bump cannot leave the pin behind. Co-Authored-By: Claude Opus 5.5 (1M context) --- AGENTS.md | 5 +++-- main.go | 35 +++++++++++++++++++++++++++-------- runtime_image_test.go | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 runtime_image_test.go diff --git a/AGENTS.md b/AGENTS.md index de3b4b0..83ff63d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -146,8 +146,9 @@ into `templates/factory/code/pyproject.toml.tmpl` yourself. (`RECIPE_INVENTORY_SCOPE_TREE`), the Dockerfile-only one claims just what it emitted. Never rewrite `pyproject.toml` or `uv.lock` to make a path resolve. - **The runtime image is pinned.** `docker-image` rejects a bare name and - `:latest`. The default `codeflydev/python` companion is built by core and - repinned on release — prefer it. + `:latest`. The default `ghcr.io/codefly-dev/python` companion is built from + core and pinned by the digest of its multi-platform index — prefer it. Its tag + must equal the linked core's companion version (test-enforced). - **A user's `flake.nix` is never overwritten.** The embedded flake is written only when the source dir has none. - **Release needs cgo.** The binary links core's tree-sitter grammars, so diff --git a/main.go b/main.go index bcf76e0..97ec008 100644 --- a/main.go +++ b/main.go @@ -79,20 +79,39 @@ type Settings struct { // RuntimeImage overrides the default codefly-built runtime image. // Format: "name:tag". Plain "name" and ":latest" are rejected — - // pinning is enforced. Leave empty to use codeflydev/python: + // pinning is enforced. Leave empty to use ghcr.io/codefly-dev/python: // (recommended; companion is rebuilt + pinned on every codefly // release). Field named RuntimeImage (not DockerImage) to avoid // colliding with services.Base.DockerImage(req). RuntimeImage string `yaml:"docker-image"` } -// runtimeImage is the codefly-built Python runtime companion — -// python:3.13.1-alpine3.21 + codefly CLI + uv 0.5.29. Built from -// core/companions/python/. Users can override via the Python settings -// (DockerImage field) but NOT recommended — the companion image is -// the mode-consistent default and gets the same tool set as every -// other codefly-built image. -var runtimeImage = &resources.DockerImage{Name: "codeflydev/python", Tag: "0.0.1"} +// runtimeImage is the codefly-built Python runtime companion, built from +// core/companions/python/ and published by the CLI to ghcr.io/codefly-dev as +// a linux/amd64 + linux/arm64 image index. Users can override it through the +// Python settings (docker-image) but should not: the companion is the +// mode-consistent default and carries the same tool set as every other +// codefly-built image. +// +// It is pinned by the digest of the multi-platform INDEX, never of one +// platform's manifest: the build recipe is rendered for every target platform +// and each resolves its own entry from the index. The legacy +// docker.io/codeflydev/python:0.0.1 this replaces was a single amd64 manifest, +// so every linux/arm64 image build ran amd64 binaries under emulation and died +// on its first RUN ("Invalid ELF image for this architecture"). +// +// Tag is the companion version of the core this agent links +// (core/companions/python/info.codefly.yaml); a test holds the two together, +// so bumping core without re-pinning here fails the build. Re-pin the digest +// from the registry when the tag moves: +// +// docker buildx imagetools inspect ghcr.io/codefly-dev/python: +var runtimeImage = &resources.DockerImage{ + Repository: resources.ImageRegistry, + Name: "python", + Tag: "0.0.5", + Digest: "sha256:67d6fe825765fc30f94131ee551e3800f5b6fa4e07094b47d036904387cb75a7", +} // uvVersion is the uv the emitted build recipe resolves its lockfile with. // diff --git a/runtime_image_test.go b/runtime_image_test.go new file mode 100644 index 0000000..abae237 --- /dev/null +++ b/runtime_image_test.go @@ -0,0 +1,33 @@ +package main + +import ( + "context" + "regexp" + "testing" + + "github.com/codefly-dev/core/companions/python" + "github.com/codefly-dev/core/resources" + "github.com/stretchr/testify/require" +) + +// The default runtime image is the Python companion of the core this agent +// links, from the registry codefly publishes to. A core bump that moves the +// companion version fails here until the pin, and its digest, move with it. +func TestRuntimeImageIsTheLinkedCoreCompanion(t *testing.T) { + companion, err := python.CompanionImage(context.Background()) + require.NoError(t, err) + require.Equal(t, companion.Repository, runtimeImage.Repository) + require.Equal(t, companion.Name, runtimeImage.Name) + require.Equal(t, companion.Tag, runtimeImage.Tag, + "re-pin runtimeImage (tag and index digest) to core's python companion") +} + +// A tag alone lets the registry move the image under a built recipe, and a +// platform manifest digest would pin every target platform to one +// architecture. The pin is a digest; that it names the multi-platform index is +// what the comment on runtimeImage records and a reviewer checks. +func TestRuntimeImageIsDigestPinned(t *testing.T) { + require.Regexp(t, regexp.MustCompile(`^sha256:[0-9a-f]{64}$`), runtimeImage.Digest) + require.Equal(t, resources.ImageRegistry+"/python@"+runtimeImage.Digest, runtimeImage.FullName(), + "the recipe's FROM must resolve by digest") +}