diff --git a/companions/proto/Dockerfile b/companions/proto/Dockerfile index baf8f559..1d757a56 100644 --- a/companions/proto/Dockerfile +++ b/companions/proto/Dockerfile @@ -76,11 +76,6 @@ COPY --from=codeflybase /bin/codefly /bin/codefly # - Go-built plugins (buf, protoc-gen-*) ARE static (CGO_ENABLED=0 # explicitly in the builder), so we COPY just those binaries # without bringing the Go runtime. -# HOME=/tmp so the companion works when run as an arbitrary non-root UID -# (codefly runs it as the host user on Linux CI). buf and npm/npx write -# their caches under $HOME; the default /root isn't writable by that user. -ENV HOME=/tmp - # python3 + a dedicated /venv host the Python facade + strip plugins. The venv # is built here (not copied from the builder) so it is bound to this stage's # Python 3.13 — the builder may ship a different Python minor version, and a @@ -146,11 +141,22 @@ RUN printf '#!/bin/sh\nexec /venv/bin/python /opt/codefly-facades/python/protoc_ # Exercise target executables in both architecture builds. A successful native # compile alone would not catch copying the host Go distribution into runtime. +# Keep installation caches under /root, then use the runtime home as a non-root +# UID. The temporary mount prevents smoke checks from baking owned caches into +# /tmp, where a different host UID must create its own Buf/Go/npm caches. +ENV HOME=/tmp +USER 65532:65532 ARG TARGETARCH -RUN test "$(go env GOARCH)" = "$TARGETARCH" && \ +RUN --mount=type=tmpfs,target=/tmp \ + test "$(go env GOARCH)" = "$TARGETARCH" && \ buf --version && \ + mkdir /tmp/proto-check && \ + printf 'syntax = "proto3";\npackage cachecheck;\nmessage Check {}\n' > /tmp/proto-check/check.proto && \ + buf build /tmp/proto-check --output /tmp/proto-check.bin && \ protoc-gen-go --version && \ protoc-gen-go-grpc --version && \ - swagger version + swagger version && \ + npm cache verify +USER 0:0 WORKDIR /workspace diff --git a/companions/proto/README.md b/companions/proto/README.md index 09c83154..af198f6b 100644 --- a/companions/proto/README.md +++ b/companions/proto/README.md @@ -63,6 +63,10 @@ it never compiles under ARM emulation. A separate target-platform Go image supplies the runtime `go`/`gofmt`, so cross-building cannot accidentally ship the build host's compiler. Each image build executes Buf, the Go generators, Swagger and the runtime Go architecture check on its target platform. +These checks run as a non-root user with temporary caches that never enter the +image. Installation caches stay under `/root`; runtime tools use `/tmp` so +Linux callers with arbitrary host UIDs can create their own Buf, Go, and npm +caches. The tagged companion suite verifies this with a different non-root UID. Upstream downloads were checked: Buf 1.73.0's prebuilt binary carries Go 1.26.8 and x/net 0.58.0, including fixed findings, and several other plugin downloads carry still older Go toolchains. Retaining @@ -111,8 +115,9 @@ may also contain handwritten Go, so only files carrying Go's standard `info.codefly.yaml` carries the image version. Version 0.0.21 removes Rust generation and its build dependencies; 0.0.22 removes emulated Go compilation. +Version 0.0.23 fixes non-root runtime caches left owned by root in 0.0.22. Publish through `codefly companion publish` using the [publication procedure](../../docs/runbooks/publish-companions.md); -changing the manifest alone does not publish an image. Core 0.19.14 selects +changing the manifest alone does not publish an image. Core 0.19.15 selects this companion. ## Python generation diff --git a/companions/proto/companion_plugins_test.go b/companions/proto/companion_plugins_test.go index 2576d5e3..cbc006c7 100644 --- a/companions/proto/companion_plugins_test.go +++ b/companions/proto/companion_plugins_test.go @@ -313,7 +313,7 @@ func TestTsFacadeLockfileMatchesTheRuntime(t *testing.T) { // neighbour — the reader then checks the wrong line, finds something plausible, // and moves on. var runbookCitations = map[string]string{ - "Dockerfile:94": "@bufbuild/protoc-gen-es@", + "Dockerfile:89": "@bufbuild/protoc-gen-es@", "flake.nix:51": "protocGenEsVersion =", "flake.nix:59": "hash =", "flake.nix:61": "npmDepsHash =", diff --git a/companions/proto/info.codefly.yaml b/companions/proto/info.codefly.yaml index 0a9d1e31..074a5d62 100644 --- a/companions/proto/info.codefly.yaml +++ b/companions/proto/info.codefly.yaml @@ -1 +1 @@ -version: 0.0.22 +version: 0.0.23 diff --git a/docs/runbooks/bump-protobuf-es.md b/docs/runbooks/bump-protobuf-es.md index 7273b091..f46111b3 100644 --- a/docs/runbooks/bump-protobuf-es.md +++ b/docs/runbooks/bump-protobuf-es.md @@ -103,7 +103,7 @@ Six places, and they must agree: |---|---|---| | `companions/proto/facades/ts/package.json` | `@bufbuild/protoplugin` | yes | | `companions/proto/facades/ts/package-lock.json` | the resolved `protoplugin` + `protobuf` closure | yes | -| `companions/proto/Dockerfile:94` | `@bufbuild/protoc-gen-es@` installed via npm | yes | +| `companions/proto/Dockerfile:89` | `@bufbuild/protoc-gen-es@` installed via npm | yes | | `companions/proto/flake.nix:51` | `protocGenEsVersion`, built from source | yes | | `companions/proto/flake.nix:59` | `hash` — the protobuf-es source tarball | **no** | | `companions/proto/flake.nix:61` | `npmDepsHash` — its npm dependency closure | **no** | diff --git a/internal/ciguard/proto_companion_test.go b/internal/ciguard/proto_companion_test.go index cff222d5..f52984ae 100644 --- a/internal/ciguard/proto_companion_test.go +++ b/internal/ciguard/proto_companion_test.go @@ -47,6 +47,20 @@ func TestSelectedProtoCompanionShipsGoimports(t *testing.T) { require.Contains(t, string(output), "goimports") } +// Linux runs companions as the host UID. Build-time checks must not leave +// root-owned caches that pass on macOS (root in Docker) and fail on Linux. +func TestSelectedProtoCompanionCachesWorkAsNonRoot(t *testing.T) { + ctx := t.Context() + testutil.RequireProtoImage(t, ctx) + img, err := proto.CompanionImage(ctx) + require.NoError(t, err) + fixture := filepath.Join(repoRoot(t), "companions", "proto", "testdata", "facade", "streaming") + output, err := exec.CommandContext(ctx, "docker", "run", "--rm", "--network", "none", + "--user", "1001:1001", "-v", fixture+":/proto-check:ro", "--entrypoint", "sh", img.FullName(), "-ec", + "buf build /proto-check --output /tmp/proto-check.bin && swagger version && npm cache verify").CombinedOutput() + require.NoError(t, err, "the selected companion must create its caches as an arbitrary host UID: %s", output) +} + // goimports names a package through `go list` in the consumer's module and // refuses a module whose go.mod requires a newer Go than the image carries, // then formats syntax only. Ask the selected image which Go it runs and hold diff --git a/version/info.codefly.yaml b/version/info.codefly.yaml index 309a17ff..aee57416 100644 --- a/version/info.codefly.yaml +++ b/version/info.codefly.yaml @@ -1 +1 @@ -version: 0.19.14 +version: 0.19.15