From 2b1234a404822688626cee6a64fd1c5e68bb3c96 Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Tue, 27 Oct 2026 15:49:52 -0400 Subject: [PATCH 1/5] feat(imagepolicy): add target policies and CGO-aware semantic options --- AGENTS.md | 4 +- code/codeserver/codeserver_test.go | 31 +++ code/codeserver/doc.go | 8 +- code/codeserver/semantics_cgo.go | 17 +- code/codeserver/semantics_nosemantic.go | 10 +- code/codeserver/semantics_nosemantic_test.go | 33 +++ companions/imagepolicy_test.go | 133 ++++++++++++ docs/cgo.md | 20 ++ docs/image-policy.md | 80 +++++++ go.mod | 14 +- go.sum | 30 ++- imagepolicy/policy.go | 209 +++++++++++++++++++ imagepolicy/policy_test.go | 179 ++++++++++++++++ imagepolicy/regression_test.go | 71 +++++++ 14 files changed, 816 insertions(+), 23 deletions(-) create mode 100644 code/codeserver/semantics_nosemantic_test.go create mode 100644 companions/imagepolicy_test.go create mode 100644 docs/image-policy.md create mode 100644 imagepolicy/policy.go create mode 100644 imagepolicy/policy_test.go create mode 100644 imagepolicy/regression_test.go diff --git a/AGENTS.md b/AGENTS.md index 47d0cfa5f..4299e1edb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -90,8 +90,7 @@ image core alone cannot build. Run them locally before touching that surface. ## Where things live -`resources/` is the source of truth for every type. When unsure how something is -modelled, read it there first. +`resources/` is the source of truth for every type; read it there first. | Path | Owns | | --- | --- | @@ -102,6 +101,7 @@ modelled, read it there first. | `network/` | deterministic port allocation, DNS, native/container/public modes | | `configurations/` | what services provide and consume, injected as env vars | | `runners/`, `companions/` | process execution; sidecar images for language tooling | +| `imagepolicy/` | target-declared Dockerfile requirements ([docs](docs/image-policy.md)) | | `solutionhost/` | `ModuleManifest`, the one signed record of a module instance on a host: its workloads, endpoints and requested authority ([docs](docs/module-manifest.md)) | | `contracts/module/` | the ONE implementation of `codefly/module-contract/v1` (Go package `module`, imported as `modulecontract`) — the request a module publishes, strict decoding, every refusal as a named rule (`rules.go`), slot resolution, a serialisable model, shipped fixtures every reader is driven through and a self-check that deletes each rule and proves a fixture notices ([docs](docs/module-contract.md)). The cell (`codefly/cell/v1`) has no implementation in core | | `code/semantic` | the **only** package allowed to use cgo (tree-sitter) | diff --git a/code/codeserver/codeserver_test.go b/code/codeserver/codeserver_test.go index cbe023c99..de82a7b9f 100644 --- a/code/codeserver/codeserver_test.go +++ b/code/codeserver/codeserver_test.go @@ -7,6 +7,7 @@ import ( "path/filepath" "testing" + "github.com/codefly-dev/core/code" "github.com/codefly-dev/core/code/codeserver" basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0" codev0 "github.com/codefly-dev/core/generated/go/codefly/services/code/v0" @@ -36,3 +37,33 @@ func TestNewInstallsSemanticAnalyzer(t *testing.T) { t.Fatalf("semantic index = %s, failure = %#v", encoded, response.GetFailure()) } } + +// TestSemanticOptionsInstallsAnalyzer verifies the options provider carries the +// same analyzer New does, so a consumer that assembles its own +// DefaultCodeServer gets source semantics without naming core/code/semantic — +// the import that put an unconditional cgo edge in an agent. The +// codefly_nosemantic variant returns no options and is exercised by the +// CGO-free build guard (scripts/check_cgo_free.sh), not here. +func TestSemanticOptionsInstallsAnalyzer(t *testing.T) { + root := t.TempDir() + if err := os.WriteFile(filepath.Join(root, "app.py"), []byte("import requests\nclass Client: pass\n"), 0o644); err != nil { + t.Fatal(err) + } + + options := codeserver.SemanticOptions() + if len(options) == 0 { + t.Fatal("SemanticOptions returned no options on a CGO build") + } + + response, err := code.NewDefaultCodeServer(root, options...).Execute(t.Context(), &codev0.CodeRequest{ + Operation: &codev0.CodeRequest_GetSemanticIndex{GetSemanticIndex: &codev0.GetSemanticIndexRequest{}}, + }) + if err != nil { + t.Fatal(err) + } + index := response.GetGetSemanticIndex() + if index == nil || index.GetState() != basev0.SemanticIndexState_SEMANTIC_INDEX_STATE_COMPLETE { + encoded, _ := protojson.Marshal(index) + t.Fatalf("semantic index = %s, failure = %#v", encoded, response.GetFailure()) + } +} diff --git a/code/codeserver/doc.go b/code/codeserver/doc.go index a5055e0d4..0d9d813ef 100644 --- a/code/codeserver/doc.go +++ b/code/codeserver/doc.go @@ -2,8 +2,12 @@ // (the CLI, the gateway) run, deciding at build time whether to link the // tree-sitter semantic analyzer. // -// New is the single entry point, so consumers never reinvent the cgo/no-cgo -// split themselves. On a normal build it installs core/code/semantic — the +// New and SemanticOptions are the only entry points, so consumers never +// reinvent the cgo/no-cgo split themselves: New when codeserver builds the +// server, SemanticOptions when the consumer builds it and needs the analyzer +// option alone. Naming core/code/semantic directly instead pins a cgo edge the +// tag cannot remove, one repo away from the guard that would have caught it. +// On a normal build it installs core/code/semantic — the // tree-sitter CGO stack — so the server answers source-semantics operations. // Built with -tags codefly_nosemantic it returns a plain DefaultCodeServer that // never imports semantic, keeping the binary CGO-free (linkable with diff --git a/code/codeserver/semantics_cgo.go b/code/codeserver/semantics_cgo.go index 42bd07e55..02d78ae5c 100644 --- a/code/codeserver/semantics_cgo.go +++ b/code/codeserver/semantics_cgo.go @@ -7,11 +7,26 @@ import ( "github.com/codefly-dev/core/code/semantic" ) +// SemanticOptions returns the ServerOption values that install the tree-sitter +// semantic analyzer. It exists because a consumer that builds its own +// DefaultCodeServer needs the options, not a finished server: without it the +// only way to reach the analyzer was to import core/code/semantic directly, +// which is how an agent acquired an unconditional cgo edge that +// scripts/check_cgo_free.sh cannot see — it guards core's packages, and the +// import was one repo away. Take this instead of naming semantic.New yourself. +// +// Importing core/code/semantic pulls the tree-sitter CGO stack, so this variant +// cannot link under CGO_ENABLED=0. Build with -tags codefly_nosemantic to +// select the CGO-free variant, which returns no options. +func SemanticOptions() []code.ServerOption { + return []code.ServerOption{code.WithSemanticAnalyzer(semantic.New())} +} + // New returns a DefaultCodeServer with the tree-sitter semantic analyzer // installed. Importing core/code/semantic pulls the tree-sitter CGO stack, so // this variant cannot link under CGO_ENABLED=0 — an accidental CGO-free build // fails loudly at link time rather than silently dropping source semantics. // Build with -tags codefly_nosemantic to select the CGO-free variant. func New(root string, opts ...code.ServerOption) *code.DefaultCodeServer { - return code.NewDefaultCodeServer(root, append(opts, code.WithSemanticAnalyzer(semantic.New()))...) + return code.NewDefaultCodeServer(root, append(opts, SemanticOptions()...)...) } diff --git a/code/codeserver/semantics_nosemantic.go b/code/codeserver/semantics_nosemantic.go index 39923b9fe..4c29c2eef 100644 --- a/code/codeserver/semantics_nosemantic.go +++ b/code/codeserver/semantics_nosemantic.go @@ -4,10 +4,18 @@ package codeserver import "github.com/codefly-dev/core/code" +// SemanticOptions returns no options: this variant never imports +// core/code/semantic, so the binary links with CGO_ENABLED=0. A server built +// from these options reports an unsupported-operation failure for +// source-semantics requests, which is the same answer New gives here. +func SemanticOptions() []code.ServerOption { + return nil +} + // New returns a plain DefaultCodeServer with no semantic analyzer. Selected by // the codefly_nosemantic build tag, this variant never imports // core/code/semantic, so the binary links with CGO_ENABLED=0. Source-semantics // operations report an unsupported-operation failure. func New(root string, opts ...code.ServerOption) *code.DefaultCodeServer { - return code.NewDefaultCodeServer(root, opts...) + return code.NewDefaultCodeServer(root, append(opts, SemanticOptions()...)...) } diff --git a/code/codeserver/semantics_nosemantic_test.go b/code/codeserver/semantics_nosemantic_test.go new file mode 100644 index 000000000..707e9e5bf --- /dev/null +++ b/code/codeserver/semantics_nosemantic_test.go @@ -0,0 +1,33 @@ +//go:build codefly_nosemantic + +package codeserver_test + +import ( + "testing" + + "github.com/codefly-dev/core/code" + "github.com/codefly-dev/core/code/codeserver" + basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0" + codev0 "github.com/codefly-dev/core/generated/go/codefly/services/code/v0" +) + +func TestSemanticOptionsWithoutCGO(t *testing.T) { + options := codeserver.SemanticOptions() + if len(options) != 0 { + t.Fatal("SemanticOptions installed an analyzer in a no-semantic build") + } + for _, server := range []*code.DefaultCodeServer{ + codeserver.New(t.TempDir()), + code.NewDefaultCodeServer(t.TempDir(), options...), + } { + response, err := server.Execute(t.Context(), &codev0.CodeRequest{ + Operation: &codev0.CodeRequest_GetSemanticIndex{GetSemanticIndex: &codev0.GetSemanticIndexRequest{}}, + }) + if err != nil { + t.Fatal(err) + } + if response.GetFailure().GetCode() != basev0.FailureCode_FAILURE_CODE_UNSUPPORTED_OPERATION { + t.Fatalf("expected unsupported semantics, got %v", response) + } + } +} diff --git a/companions/imagepolicy_test.go b/companions/imagepolicy_test.go new file mode 100644 index 000000000..d44342ea1 --- /dev/null +++ b/companions/imagepolicy_test.go @@ -0,0 +1,133 @@ +package companions_test + +import ( + "errors" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/codefly-dev/core/companions" + "github.com/codefly-dev/core/imagepolicy" + "github.com/stretchr/testify/require" +) + +// unpinnedUpstreamBases are the companion bases that still float on a tag. +// +// Every entry is a build whose inputs change under it without anything saying +// so: the tag moves, the next build is different bytes, and a scan of the +// previous one described an image that no longer exists. These are build-time +// images rather than deployed ones, which is why they were never caught — but +// they are what compiles and generates user code. +// +// The list exists so the invariant can be turned on NOW, before the pinning is +// done, instead of waiting for it. Adding an entry is a deliberate, reviewed +// decision and the only correct direction is down. Pinning one means deleting +// its line: TestEveryAllowedUnpinnedBaseIsStillUnpinned fails on an entry that +// no longer applies, so the list cannot quietly outlive the debt. +// +// Pinning these is not a drive-by edit. Companion images are a separate release +// contract — built from their own manifest and published by the CLI, not by the +// core release flow — so moving a base means republishing the companion. See +// docs/runbooks/publish-companions.md. +var unpinnedUpstreamBases = []string{ + "alpine:3.24.2", + "docker:29-dind", + "golang:1.27-alpine", + "node:26.3.0-alpine3.22", + "python:3.14.1-alpine3.21", + "rust:1.99-alpine", +} + +// corePolicy is what core requires of its own images. It is an ordinary +// imagepolicy.Policy, the same shape a deployment target declares, so this test +// also demonstrates the package against real Dockerfiles rather than fixtures. +// +// No registry allow-list: where a companion is published is the builder's +// decision, and TestRegistryLiteralsAreConfinedToTheBaseImageDefault already +// owns the one reference core still carries. No required labels: a companion is +// not deployed into a customer's cluster, so a compliance programme's labels do +// not apply to it. +var corePolicy = imagepolicy.Policy{RequireDigest: true} + +// refusals runs the policy over one companion and drops the base-image +// argument's default. +// +// That default is a tag on purpose: the publisher overrides it with the digest +// it just pushed, so the tag in the file is not a claim about what gets built. +// The same carve-out, for the same reason, as the registry-literal test. +func refusals(t *testing.T, spec companions.BuildSpec) []imagepolicy.Refusal { + t.Helper() + content := dockerfile(t, spec) + baseDefault := "" + for _, instruction := range companions.Instructions(content) { + text := strings.TrimSpace(instruction.Text) + if prefix := "ARG " + companions.BaseImageArg + "="; strings.HasPrefix(text, prefix) { + baseDefault = strings.Trim(strings.TrimPrefix(text, prefix), `"'`) + } + } + var kept []imagepolicy.Refusal + for _, refusal := range imagepolicy.Check(content, corePolicy) { + if spec.Base != "" && baseDefault != "" && refusal.Detail == baseDefault && errors.Is(refusal, imagepolicy.ErrUnpinnedBase) { + continue + } + kept = append(kept, refusal) + } + return kept +} + +// These files ship scratch-based builders and the publish action's smoke image. +// Apply the same policy so replacing scratch with a floating base fails here. +func TestCoreImageTemplatesSatisfyPolicy(t *testing.T) { + for _, path := range []string{ + "runners/golang/templates/builder/Dockerfile.tmpl", + "runners/rust/templates/builder/Dockerfile.tmpl", + ".github/actions/publish-service-image/testdata/Dockerfile", + } { + t.Run(path, func(t *testing.T) { + content, err := os.ReadFile(filepath.Join(repositoryRoot(t), path)) + require.NoError(t, err) + require.Empty(t, imagepolicy.Check(string(content), corePolicy)) + }) + } +} + +// A companion base that is neither pinned nor a known debt entry is a new +// floating input, and it fails here rather than in whatever build first +// notices the tag moved. +func TestNoNewUnpinnedCompanionBase(t *testing.T) { + allowed := make(map[string]bool, len(unpinnedUpstreamBases)) + for _, base := range unpinnedUpstreamBases { + allowed[base] = true + } + for _, spec := range buildSpecs(t) { + for _, refusal := range refusals(t, spec) { + // The debt list excuses a floating tag and nothing else: any other + // rule failing is a finding whatever the reference is. + if errors.Is(refusal, imagepolicy.ErrUnpinnedBase) && allowed[refusal.Detail] { + continue + } + require.Failf(t, "companion image policy", + "%s:%d %v. Pin the base by digest; add it to unpinnedUpstreamBases "+ + "only as a reviewed exception.", + spec.Dockerfile, refusal.Line, refusal) + } + } +} + +// The debt list shrinks or it rots. An entry that no longer appears unpinned in +// any companion has been pinned (or removed), and leaving it behind would let +// the next floating base of the same name back in unnoticed. +func TestEveryAllowedUnpinnedBaseIsStillUnpinned(t *testing.T) { + seen := map[string]bool{} + for _, spec := range buildSpecs(t) { + for _, refusal := range refusals(t, spec) { + seen[refusal.Detail] = true + } + } + for _, base := range unpinnedUpstreamBases { + require.Truef(t, seen[base], + "unpinnedUpstreamBases lists %q, but no companion names it unpinned any more. "+ + "Delete the entry in the change that pinned it.", base) + } +} diff --git a/docs/cgo.md b/docs/cgo.md index 09d4ca04e..0f7cfa8e9 100644 --- a/docs/cgo.md +++ b/docs/cgo.md @@ -51,6 +51,26 @@ srv := codeserver.New(root) // + optional code.ServerOption values `CGO_ENABLED=0`. Source-semantics operations report an unsupported-operation failure. +### When you build the server yourself + +A consumer that assembles its own `DefaultCodeServer` — because it adds options +of its own, or wraps the server — needs the *options*, not a finished server. +Take them from `codeserver`, which splits on the same tag: + +```go +opts := append(myOptions, codeserver.SemanticOptions()...) +srv := code.NewDefaultCodeServer(root, opts...) +``` + +`SemanticOptions` returns the analyzer option on a default build and **nothing** +under `-tags codefly_nosemantic`. Never name `semantic.New()` to fill this gap: +that import is unconditional, so it pins a cgo edge the tag cannot remove. It is +also invisible to `scripts/check_cgo_free.sh`, which proves *core's* packages +link CGO-free and cannot see a consumer's import graph — so the break surfaces +as a failed `CGO_ENABLED=0` link in another repo, long after the import landed. +That is not hypothetical: it is how the service agents came to require cgo, and +why a static (scratch-based) agent image was not buildable. + `GetProjectInfo` is the exception, because it is not a source-semantics operation: it reports dependencies, packages and file hashes, and only its per-file import inventory needs a parser. Go takes that inventory with diff --git a/docs/image-policy.md b/docs/image-policy.md new file mode 100644 index 000000000..1be48cc36 --- /dev/null +++ b/docs/image-policy.md @@ -0,0 +1,80 @@ +# Image policy + +`imagepolicy.Check(dockerfile, policy)` checks Dockerfile defaults against a +policy supplied by a deployment target. Requirements are data: Core contains +no customer registry or compliance-label roster. A zero `Policy` requires +nothing and performs no parsing. + +## Rules + +The checker returns all base-reference refusals in source order, followed by +missing final-stage labels in policy order. Each `Refusal` carries a line +(zero for file-wide findings), detail and a named error usable with `errors.Is`. + +| Policy | Refusal | Meaning | +| --- | --- | --- | +| `RequireDigest` | `ErrUnpinnedBase` | a FROM base has no valid content digest | +| `AllowedRegistries` | `ErrRegistryNotAllowed` | a FROM base is outside the allowed registry hosts/repository paths | +| `RequiredLabels` | `ErrMissingLabel` | the final stage lacks a required label with a known, non-empty value | +| either reference rule | `ErrUnresolvedBase` | a base is invalid or depends on an argument without a known default | +| any active policy | `ErrInvalidDockerfile` | BuildKit cannot parse the Dockerfile, or it has no stage | + +Registry matching uses normalized image names and complete path components: +`registry.example/team` accepts `registry.example/team/app`, but neither +`registry.example/team-evil/app` nor `registry.example.evil/team/app`. +`alpine` normalizes to `docker.io/library/alpine`. An empty registry list permits +any registry; an empty entry in a non-empty list permits none. + +The checker reuses BuildKit's Dockerfile parser, instruction model and shell +expander, plus `distribution/reference` for image names and digests. This keeps +heredoc contents out of the instruction stream and handles continuation lines, +escape directives and quoted or legacy label syntax without another parser. + +`scratch` needs neither a digest nor a registry. A reference to an earlier stage +inherits that stage's labels and variables; naming a stage does not exempt its +own external base. Only global ARG defaults resolve FROM instructions. Stage +ARG/ENV values resolve labels, with ENV taking precedence. Unrelated builder +labels do not satisfy the final stage, and an empty or unknown overwrite removes +a label's known value. Inherited stage maps are copied so sibling stages cannot +change one another's labels. + +## Source-check boundary + +This is a check of **Dockerfile defaults**, not a certificate for a built image. +It checks all FROM instructions and the last stage's labels. It does not read +external base-image labels, build argument overrides, automatic platform +arguments, named build contexts, external COPY/ADD sources or an alternate +`--target`. Unknown argument values (including shell fallback expressions over +undeclared variables) are refused conservatively when a reference rule needs +them. Declare defaults explicitly when checking source. + +A build integration must validate its effective inputs and selected target. +Core does not yet connect this package to CLI builds, carry target policy in +coordinate declarations, or switch vulnerability auditing to enforcement. +Those changes belong to the CLI and policy producers, once the build location +and policy delivery are decided; see [configuration-contract.md](configuration-contract.md). + +Artifact scanning is complementary. The existing `builderv0.AuditRequest` +`fail_on_vuln` flag asks for `state=ERROR` on HIGH-or-above findings and defaults +to false (report-only). `agents/services.BuilderWrapper.AuditContainer` applies +that contract to Trivy image reports. A target needs both source/input checks +and artifact auditing; passing this package alone does not prove an image is +vulnerability-free or compliant. + +## Core's own images + +`companions/imagepolicy_test.go` runs `RequireDigest` over every companion +Dockerfile, both runner builder templates (`runners/golang` and `runners/rust`), +and the publish-service-image action's smoke-test Dockerfile. The latter three +use scratch and require no exceptions. + +The companion base-image argument's tag default is exempted only from the +unpinned-base refusal: the publisher overrides it with the digest it just +published. Existing companion build-spec tests enforce that default's shape. + +Six distinct floating upstream bases remain explicitly listed in +`unpinnedUpstreamBases`. This is a **temporary stopgap**, not completed pinning: +new references fail and stale exceptions fail once no companion uses them. +Pinning them requires the companion build/publish contract in +[runbooks/publish-companions.md](runbooks/publish-companions.md). No companion +images are rebuilt or published by adding this guard. diff --git a/go.mod b/go.mod index dd72734c9..f9d4043e9 100644 --- a/go.mod +++ b/go.mod @@ -37,6 +37,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.31.0 github.com/hashicorp/go-multierror v1.1.1 github.com/mitchellh/mapstructure v1.5.0 + github.com/moby/buildkit v0.34.1 github.com/moby/patternmatcher v0.6.1 github.com/muesli/termenv v0.16.0 github.com/opencontainers/image-spec v1.1.1 @@ -88,9 +89,10 @@ require ( code.gitea.io/sdk/gitea v0.23.2 // indirect dario.cat/mergo v1.0.2 // indirect github.com/42wim/httpsig v1.2.4 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 // indirect github.com/ProtonMail/go-crypto v1.4.1 // indirect github.com/VividCortex/ewma v1.2.0 // indirect + github.com/agext/levenshtein v1.2.3 // indirect github.com/alecthomas/chroma/v2 v2.23.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect @@ -106,7 +108,7 @@ require ( github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/cloudflare/circl v1.6.3 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/containerd/log v0.1.0 // indirect + github.com/containerd/typeurl/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect github.com/dlclark/regexp2 v1.12.0 // indirect @@ -160,23 +162,22 @@ require ( github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/sys/atomicwriter v0.1.0 // indirect - github.com/moby/sys/sequential v0.7.0 // indirect github.com/moby/term v0.5.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect - github.com/morikuni/aec v1.1.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/oklog/ulid/v2 v2.1.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pjbgf/sha1cd v0.7.0 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sergi/go-diff v1.4.0 // indirect github.com/shoenig/go-m1cpu v0.2.1 // indirect - github.com/sirupsen/logrus v1.9.4 // indirect github.com/skeema/knownhosts v1.3.2 // indirect github.com/tklauser/numcpus v0.12.0 // indirect + github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xlab/treeprint v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect @@ -185,7 +186,6 @@ require ( gitlab.com/gitlab-org/api/client-go v1.46.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 // indirect go.opentelemetry.io/otel/log v1.47.0 // indirect go.opentelemetry.io/otel/metric v1.47.0 // indirect go.opentelemetry.io/proto/otlp v1.11.0 // indirect @@ -195,7 +195,7 @@ require ( golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.23.0 // indirect - golang.org/x/time v0.15.0 // indirect + golang.org/x/time v0.16.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gotest.tools/v3 v3.5.1 // indirect diff --git a/go.sum b/go.sum index 1b37d362f..d318e05ff 100644 --- a/go.sum +++ b/go.sum @@ -21,12 +21,14 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 h1:0kQAzHq8vLs7Pptv+7TxjdETLf/nIqJpIB4oC6Ba4vY= +github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0= github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM= github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY= @@ -91,8 +93,10 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/log v0.2.0 h1:BewD/umNgVnoczglOpX8eRMyEy5t5iPlu5AIpnWDONc= +github.com/containerd/log v0.2.0/go.mod h1:/M7L7CXKcPTfNC74XzaK+5H5KbO5+4lJVpuVI6vRLoM= +github.com/containerd/typeurl/v2 v2.3.0 h1:HZHPhRWo5XMy3QGQoPrUzbW/2ckwjfweHmOwlkIrPAQ= +github.com/containerd/typeurl/v2 v2.3.0/go.mod h1:Qk+PAdUYArVj41TnGi6rJ+48RF0PkcTc4i/taoBcK0w= github.com/creativeprojects/go-selfupdate v1.6.0 h1:Bu3cIgdyfI1Pg8XsL8nbaT2uMjfZ8HIoxnBmPJbN0sw= github.com/creativeprojects/go-selfupdate v1.6.0/go.mod h1:Ids8O474XGQG0jZ5vpBIhWffcGYjUP6ccOI0mMcvQbI= github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE= @@ -266,6 +270,8 @@ github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwX github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/buildkit v0.34.1 h1:jVdxsXX0UpU0q1Et+9lFezPmh9sGE6vF0TrKZaGlfy4= +github.com/moby/buildkit v0.34.1/go.mod h1:e+Za70y2a3nL1U2Y0P8TvhPyKp2Ok2NS+Q4siSArDiM= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= @@ -303,6 +309,8 @@ github.com/pjbgf/sha1cd v0.7.0 h1:ZRNPKHj+gfkLBf0KJv/p1Hmz+7bqCT5o311YX0nq+DA= github.com/pjbgf/sha1cd v0.7.0/go.mod h1:pKR5Li+qTCo+ebqITZfwPlJGoCFCvSO00qxjbNtTUFQ= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= @@ -327,8 +335,8 @@ github.com/shoenig/go-m1cpu v0.2.1/go.mod h1:KkDOw6m3ZJQAPHbrzkZki4hnx+pDRR1Lo+l github.com/shoenig/test v1.7.0 h1:eWcHtTXa6QLnBvm0jgEabMRN/uJ4DMV3M8xUGgRkZmk= github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= -github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo= +github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q= github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -343,6 +351,8 @@ github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRU github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI= github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4= github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg= +github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 h1:2f304B10LaZdB8kkVEaoXvAMVan2tl9AiK4G0odjQtE= +github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE= github.com/tree-sitter-grammars/tree-sitter-kotlin v1.1.0 h1:SWIUDASa+WPhDDem1U5IJpYwQEezkqXrUI61OcnORzM= github.com/tree-sitter-grammars/tree-sitter-kotlin v1.1.0/go.mod h1:eH+flFf3QOa9c9BY9g3Bz02F7zTq30kGIG3cgB0lSlI= github.com/tree-sitter/go-tree-sitter v0.25.0 h1:sx6kcg8raRFCvc9BnXglke6axya12krCJF5xJ2sftRU= @@ -405,8 +415,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0 h1:julhjPeUH/q/7hinbSd go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0/go.mod h1:Ao2mz688LH/tFf0yMAenidq6k2YNSx6SIY2q6jDACck= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0 h1:UFpxOpYPmMNUtOWhdb+nC1WELIgVf8z9higURrbzYU4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0/go.mod h1:YBGjxe3lt0jtXoEXxGrGhv/jM7oUBUDVK7zhAvNyEjc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 h1:QBajQ2SrwQijzHyZbQlPsuIzpl/ll8DY6wPWsajeGcI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0/go.mod h1:08ZQLjrPLQ6R4kAXvuOvODEer5Yh4CoFvll5qB2BCI8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0 h1:KrC1YrQeSt46ITMWAbgQx1M1eV1/1TKzttrBzymPmss= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0/go.mod h1:zDSEzoEqsOrgBeGvH66KRgxh90VonFyJqBHA0Pk3+rM= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.47.0 h1:N3YQCxjxQ/bMjyc3heladfRm9t9RTksGQH8z4w6yU/0= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.47.0/go.mod h1:Mp8HOFqcaUyypCuGv9IhDdTHnJ56lSudSHMd+pVSCEA= go.opentelemetry.io/otel/log v1.47.0 h1:cOTS1CcLbSQeZKanGJ+0JpF/+t4PELi3O3bbl2lqCcI= @@ -468,8 +478,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= -golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= -golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE= +golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= diff --git a/imagepolicy/policy.go b/imagepolicy/policy.go new file mode 100644 index 000000000..42ac0c345 --- /dev/null +++ b/imagepolicy/policy.go @@ -0,0 +1,209 @@ +// Package imagepolicy checks Dockerfile defaults against requirements supplied +// by a deployment target. It does not build images or replace artifact scanning. +package imagepolicy + +import ( + _ "crypto/sha256" // register the image digest algorithm for reference.Parse + "errors" + "fmt" + "maps" + "strings" + + "github.com/distribution/reference" + "github.com/moby/buildkit/frontend/dockerfile/instructions" + "github.com/moby/buildkit/frontend/dockerfile/parser" + "github.com/moby/buildkit/frontend/dockerfile/shell" +) + +var ( + // ErrUnpinnedBase identifies a base without a content digest. + ErrUnpinnedBase = errors.New("base image is not pinned by digest") + // ErrRegistryNotAllowed identifies a base outside the allowed registry paths. + ErrRegistryNotAllowed = errors.New("base image registry is not allowed") + // ErrMissingLabel identifies a required final-stage label without a known value. + ErrMissingLabel = errors.New("required label is missing") + // ErrUnresolvedBase identifies a base with an unknown default or invalid reference. + ErrUnresolvedBase = errors.New("base image reference cannot be resolved") + // ErrInvalidDockerfile identifies input the Dockerfile parser cannot read. + ErrInvalidDockerfile = errors.New("invalid Dockerfile") +) + +// Policy is what a deployment target requires. A zero Policy requires nothing. +type Policy struct { + // AllowedRegistries contains registry hosts, optionally narrowed to repository + // paths (for example registry.example/team). Matching respects path boundaries. + // Empty permits any registry; familiar references are normalized to docker.io. + AllowedRegistries []string + // RequireDigest refuses a base named by tag alone. + RequireDigest bool + // RequiredLabels contains keys the final stage must set to known non-empty + // values, directly or through an earlier stage in this Dockerfile. + RequiredLabels []string +} + +// Refusal is a named rule failing at a source line (zero for file-wide failures). +type Refusal struct { + Rule error + Line int + Detail string +} + +func (r Refusal) Error() string { + return fmt.Sprintf("line %d: %s: %s", r.Line, r.Rule, r.Detail) +} +func (r Refusal) Unwrap() error { return r.Rule } + +// Check returns all reference refusals, followed by missing final-stage labels. +// It checks every FROM using global ARG defaults, and the last stage's labels. +// Build argument overrides, alternate build targets, external image metadata and +// COPY --from images are outside this source check. Callers must also validate +// effective build inputs and scan the artifact before enforcing deployment policy. +func Check(dockerfile string, policy Policy) []Refusal { + if !policy.RequireDigest && len(policy.AllowedRegistries) == 0 && len(policy.RequiredLabels) == 0 { + return nil + } + parsed, err := parser.Parse(strings.NewReader(dockerfile)) + if err != nil { + return []Refusal{{Rule: ErrInvalidDockerfile, Detail: err.Error()}} + } + stages, metaArgs, err := instructions.Parse(parsed.AST, nil) + if err != nil { + return []Refusal{{Rule: ErrInvalidDockerfile, Detail: err.Error()}} + } + if len(stages) == 0 { + return []Refusal{{Rule: ErrInvalidDockerfile, Detail: "no build stage"}} + } + lex := shell.NewLex(parsed.EscapeToken) + lex.SkipUnsetEnv = true // retain unknown variables rather than certifying an empty substitution + global := values{} + for _, command := range metaArgs { + applyArgs(lex, command, global, nil, nil) + } + var refusals []Refusal + previous := map[string]stageState{} + var current stageState + for _, stage := range stages { + base, resolved := expand(lex, stage.BaseName, global) + parent, internal := previous[strings.ToLower(base)] + current = stageState{labels: values{}, args: values{}, env: values{}} + if resolved && internal { + current = stageState{maps.Clone(parent.labels), maps.Clone(parent.args), maps.Clone(parent.env)} + } else if policy.RequireDigest || len(policy.AllowedRegistries) > 0 { + line := stage.Location[0].Start.Line + if !resolved { + refusals = append(refusals, Refusal{ErrUnresolvedBase, line, stage.BaseName}) + } else { + refusals = append(refusals, checkReference(base, line, policy)...) + } + } + for _, command := range stage.Commands { + switch command := command.(type) { + case *instructions.ArgCommand: + applyArgs(lex, *command, current.args, current.env, global) + case *instructions.EnvCommand: + applyPairs(lex, command.Env, current.env, current.variables()) + case *instructions.LabelCommand: + applyPairs(lex, command.Labels, current.labels, current.variables()) + } + } + // Register only after checking the base: FROM alpine AS alpine still + // names an external image, not itself. + if stage.Name != "" { + previous[strings.ToLower(stage.Name)] = current + } + } + for _, key := range policy.RequiredLabels { + if value, known := current.labels.Get(key); !known || value == "" { + refusals = append(refusals, Refusal{ErrMissingLabel, 0, key}) + } + } + return refusals +} + +func checkReference(base string, line int, policy Policy) []Refusal { + if base == "scratch" { + return nil + } + ref, err := reference.ParseNormalizedNamed(base) + if err != nil { + return []Refusal{{ErrUnresolvedBase, line, base}} + } + var refusals []Refusal + if _, pinned := ref.(reference.Digested); policy.RequireDigest && !pinned { + refusals = append(refusals, Refusal{ErrUnpinnedBase, line, base}) + } + if len(policy.AllowedRegistries) > 0 && !allowed(ref.Name(), policy.AllowedRegistries) { + refusals = append(refusals, Refusal{ErrRegistryNotAllowed, line, base}) + } + return refusals +} + +func allowed(name string, registries []string) bool { + for _, registry := range registries { + registry = strings.TrimSuffix(registry, "/") + if registry != "" && (name == registry || strings.HasPrefix(name, registry+"/")) { + return true + } + } + return false +} + +// A nil value shadows an inherited value whose replacement is unknown. +type values map[string]*string + +func (v values) Get(key string) (string, bool) { + if value := v[key]; value != nil { + return *value, true + } + return "", false +} +func (v values) Keys() []string { + keys := make([]string, 0, len(v)) + for key := range v { + keys = append(keys, key) + } + return keys +} + +type stageState struct{ labels, args, env values } + +func (s stageState) variables() values { + merged := maps.Clone(s.args) + maps.Copy(merged, s.env) // ENV takes precedence over ARG + return merged +} + +func expand(lex *shell.Lex, word string, env values) (string, bool) { + value, unmatched, err := lex.ProcessWord(word, env) + return value, err == nil && len(unmatched) == 0 +} + +func applyArgs(lex *shell.Lex, command instructions.ArgCommand, args, env, global values) { + for _, arg := range command.Args { + if arg.Value == nil { + if value, ok := global[arg.Key]; ok { + args[arg.Key] = value + } + continue + } + variables := maps.Clone(args) + maps.Copy(variables, env) + if value, ok := expand(lex, *arg.Value, variables); ok { + args[arg.Key] = &value + } else { + args[arg.Key] = nil + } + } +} + +func applyPairs(lex *shell.Lex, pairs instructions.KeyValuePairs, dst, env values) { + for _, pair := range pairs { + key, keyOK := expand(lex, pair.Key, env) + value, valueOK := expand(lex, pair.Value, env) + if keyOK && valueOK { + dst[key] = &value + } else { + dst[key] = nil + } + } +} diff --git a/imagepolicy/policy_test.go b/imagepolicy/policy_test.go new file mode 100644 index 000000000..a08912a3d --- /dev/null +++ b/imagepolicy/policy_test.go @@ -0,0 +1,179 @@ +package imagepolicy_test + +import ( + "errors" + "testing" + + "github.com/codefly-dev/core/imagepolicy" +) + +// Every Dockerfile body below is the shape a real image in the fleet is built +// from, reduced to the instructions the policy reads. Checking invented shapes +// would prove the regexes work; checking these proves the rules land on what we +// actually ship. + +// pinned is the service-go-grpc runtime stage: a digest-pinned Alpine with no +// label of any kind. It satisfies a digest rule and fails a label rule, which +// is exactly the fleet's position today. +const pinned = ` +FROM alpine:3.23.5@sha256:fd791d74b68913cbb027c6546007b3f0d3bc45125f797758156952bc2d6daf40 +RUN apk add --no-cache ca-certificates +USER 65532:65532 +CMD ["./app"] +` + +func rules(refusals []imagepolicy.Refusal) []error { + out := make([]error, 0, len(refusals)) + for _, refusal := range refusals { + out = append(out, refusal.Rule) + } + return out +} + +func requireRules(t *testing.T, refusals []imagepolicy.Refusal, want ...error) { + t.Helper() + got := rules(refusals) + if len(got) != len(want) { + t.Fatalf("got %d refusals %v, want %d %v", len(got), got, len(want), want) + } + for i, rule := range want { + if !errors.Is(refusals[i], rule) { + t.Fatalf("refusal %d = %v, want %v", i, refusals[i], rule) + } + } +} + +func TestDigestPinnedBasePasses(t *testing.T) { + requireRules(t, imagepolicy.Check(pinned, imagepolicy.Policy{RequireDigest: true})) +} + +// service-rust's runtime and obin service-xlsx both name a floating tag. These +// are not merely unscanned, they are unreproducible: the bytes behind the tag +// move, so a clean scan describes an image that may no longer exist. +func TestFloatingTagIsRefused(t *testing.T) { + for _, body := range []string{"FROM debian:bookworm-slim\n", "FROM node:26-slim\n", "FROM golang:1.27\n"} { + requireRules(t, imagepolicy.Check(body, imagepolicy.Policy{RequireDigest: true}), imagepolicy.ErrUnpinnedBase) + } +} + +// scratch has no registry to allow and no digest to pin, so the reference +// rules cannot apply. This is the whole reason a scratch image satisfies a +// registry mandate with nothing to migrate. +func TestScratchSatisfiesEveryReferenceRule(t *testing.T) { + body := "FROM scratch\nCOPY app /app\nUSER 65532:65532\n" + requireRules(t, imagepolicy.Check(body, imagepolicy.Policy{ + RequireDigest: true, + AllowedRegistries: []string{"nylartifactory.jfrog.io/devsecops-dkr-prod-virtual"}, + })) +} + +// service-postgres, service-redis, minio and service-vault all write +// `ARG X=` then `FROM ${X}`. A reader that skipped the substitution +// would pass all four without ever seeing a reference. +func TestBaseFromArgumentDefaultIsResolved(t *testing.T) { + body := ` +ARG POSTGRES_IMAGE=postgres:17.10-alpine3.24@sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193 +FROM ${POSTGRES_IMAGE} AS runtime +` + requireRules(t, imagepolicy.Check(body, imagepolicy.Policy{RequireDigest: true})) + + floating := "ARG REDIS_IMAGE=redis:8.8.0-alpine\nFROM ${REDIS_IMAGE}\n" + requireRules(t, imagepolicy.Check(floating, imagepolicy.Policy{RequireDigest: true}), imagepolicy.ErrUnpinnedBase) +} + +// An argument with no default in the file leaves the base genuinely unknown. +// Reporting that is not pedantry: passing it would certify a file that does +// not say what it builds on. +func TestUnresolvableBaseIsReported(t *testing.T) { + requireRules(t, imagepolicy.Check("FROM ${SUPPLIED_ELSEWHERE}\n", imagepolicy.Policy{RequireDigest: true}), + imagepolicy.ErrUnresolvedBase) +} + +// service-nextjs builds four stages on one another (`FROM base AS deps`). A +// stage name is an edge inside the file, not a registry reference, and holding +// it to the reference rules would refuse every multi-stage build we have. +func TestStageReferencesAreNotTreatedAsRegistries(t *testing.T) { + body := ` +FROM node:24.17.0-alpine3.23@sha256:7c70d1235c0b4c2bc9eeed5393d19f1bbdde6885ba0d58ba62bb385d7b0f3ff1 AS base +FROM base AS deps +FROM base AS builder +FROM base AS runner +COPY --from=builder /app /app +` + requireRules(t, imagepolicy.Check(body, imagepolicy.Policy{RequireDigest: true})) +} + +// service-vault and minio put --platform=$BUILDPLATFORM before the reference. +// Reading the flag as the base would make every one of them unresolvable. +func TestPlatformFlagIsNotTheReference(t *testing.T) { + body := "FROM --platform=$BUILDPLATFORM golang:1.26.10-alpine3.24@sha256:9ce231b28998b32e116927c61aa5c406183061d4f3f2d61b292c88e7f6bdf7ca AS source\n" + requireRules(t, imagepolicy.Check(body, imagepolicy.Policy{RequireDigest: true})) +} + +// The registry rule is per target and the lists conflict between targets, so +// the same file passes one policy and fails another. Nothing here is written +// for a particular customer. +func TestRegistryAllowListIsPerTarget(t *testing.T) { + body := "FROM alpine:3.23.5@sha256:fd791d74b68913cbb027c6546007b3f0d3bc45125f797758156952bc2d6daf40\n" + + mirrored := imagepolicy.Policy{AllowedRegistries: []string{"example.jfrog.io/virtual"}} + requireRules(t, imagepolicy.Check(body, mirrored), imagepolicy.ErrRegistryNotAllowed) + + remapped := "FROM example.jfrog.io/virtual/alpine:3.23.5@sha256:fd791d74b68913cbb027c6546007b3f0d3bc45125f797758156952bc2d6daf40\n" + requireRules(t, imagepolicy.Check(remapped, mirrored)) + + // A target that names no registries permits any, so a policy opts in. + requireRules(t, imagepolicy.Check(body, imagepolicy.Policy{})) +} + +// A compliance programme supplies its label keys as data. Today every template +// that renders a deployed service image sets no label at all, so all of them +// are reported, each once, rather than the first one found. +func TestRequiredLabelsAreReportedIndividually(t *testing.T) { + required := []string{"APPID", "base.name", "Technology"} + refusals := imagepolicy.Check(pinned, imagepolicy.Policy{RequiredLabels: required}) + requireRules(t, refusals, imagepolicy.ErrMissingLabel, imagepolicy.ErrMissingLabel, imagepolicy.ErrMissingLabel) + for i, refusal := range refusals { + if refusal.Detail != required[i] { + t.Fatalf("refusal %d names %q, want %q", i, refusal.Detail, required[i]) + } + } +} + +// Labels survive continuation lines and quoted values with spaces, which is +// how the postgres bootstrap template already writes its provenance block. +func TestLabelsAreReadAcrossContinuationsAndQuotes(t *testing.T) { + body := ` +FROM alpine:3.23.5@sha256:fd791d74b68913cbb027c6546007b3f0d3bc45125f797758156952bc2d6daf40 +LABEL APPID="APP-123" \ + description="Image description and app details" \ + Technology="Go" +LABEL base.name="example.jfrog.io/virtual/alpine:3.23.5" +` + requireRules(t, imagepolicy.Check(body, imagepolicy.Policy{ + RequiredLabels: []string{"APPID", "description", "Technology", "base.name"}, + })) +} + +// A label present but empty is not set: it would satisfy a grep and tell a +// compliance reader nothing. +func TestEmptyLabelDoesNotSatisfyTheRule(t *testing.T) { + body := "FROM scratch\nLABEL APPID=\"\"\n" + requireRules(t, imagepolicy.Check(body, imagepolicy.Policy{RequiredLabels: []string{"APPID"}}), + imagepolicy.ErrMissingLabel) +} + +// A zero Policy requires nothing, so a target inherits no rule it did not ask +// for and adding a rule here cannot retroactively fail an existing target. +func TestZeroPolicyRequiresNothing(t *testing.T) { + requireRules(t, imagepolicy.Check("FROM debian:bookworm-slim\n", imagepolicy.Policy{})) +} + +// A refusal carries the line so the author is sent to the instruction. +func TestRefusalCarriesTheLine(t *testing.T) { + body := "# a comment\n\nFROM debian:bookworm-slim\n" + refusals := imagepolicy.Check(body, imagepolicy.Policy{RequireDigest: true}) + if len(refusals) != 1 || refusals[0].Line != 3 { + t.Fatalf("refusals = %v, want one on line 3", refusals) + } +} diff --git a/imagepolicy/regression_test.go b/imagepolicy/regression_test.go new file mode 100644 index 000000000..7e8b7e51a --- /dev/null +++ b/imagepolicy/regression_test.go @@ -0,0 +1,71 @@ +package imagepolicy_test + +import ( + "testing" + + "github.com/codefly-dev/core/imagepolicy" +) + +func TestRegistryBoundaries(t *testing.T) { + policy := imagepolicy.Policy{AllowedRegistries: []string{"registry.example/team"}} + for _, base := range []string{"registry.example/team-evil/app", "registry.example/team.evil/app"} { + t.Run(base, func(t *testing.T) { + requireRules(t, imagepolicy.Check("FROM "+base, policy), imagepolicy.ErrRegistryNotAllowed) + }) + } + requireRules(t, imagepolicy.Check("FROM registry.example.evil/team/app", imagepolicy.Policy{ + AllowedRegistries: []string{"registry.example"}, + }), imagepolicy.ErrRegistryNotAllowed) + requireRules(t, imagepolicy.Check("FROM registry.example/team/app", policy)) + requireRules(t, imagepolicy.Check("FROM alpine", imagepolicy.Policy{AllowedRegistries: []string{"docker.io/library"}})) +} + +func TestStageScope(t *testing.T) { + for _, body := range []string{ + "FROM alpine AS alpine", + "ARG BASE=alpine\nFROM ${BASE} AS alpine", + } { + requireRules(t, imagepolicy.Check(body, imagepolicy.Policy{RequireDigest: true}), imagepolicy.ErrUnpinnedBase) + } + requireRules(t, imagepolicy.Check("FROM scratch AS build\nARG BASE=scratch\nFROM ${BASE}", imagepolicy.Policy{RequireDigest: true}), imagepolicy.ErrUnresolvedBase) +} + +func TestFinalImageLabels(t *testing.T) { + policy := imagepolicy.Policy{RequiredLabels: []string{"APPID"}} + for _, body := range []string{ + "FROM scratch AS build\nLABEL APPID=build-only\nFROM scratch", + "FROM scratch\nLABEL APPID=present\nLABEL APPID=\"\"", + "FROM scratch\nLABEL APPID=$UNDEFINED", + "FROM scratch\nARG ID=app\nENV ID=$UNDEFINED\nLABEL APPID=prefix-$ID", + "FROM scratch\nRUN < Date: Wed, 28 Oct 2026 11:50:43 -0400 Subject: [PATCH 2/5] fix(companions): refresh runtime bases and qualify unpublished companions --- .github/workflows/companions-build.yml | 28 +++++++---- companions/codefly/Dockerfile | 14 +++--- companions/codefly/info.codefly.yaml | 2 +- companions/go/Dockerfile | 2 +- companions/go/info.codefly.yaml | 2 +- companions/imagepolicy_test.go | 2 - companions/node/Dockerfile | 2 +- companions/node/info.codefly.yaml | 2 +- companions/proto/Dockerfile | 2 +- companions/proto/info.codefly.yaml | 2 +- companions/python/Dockerfile | 13 +++-- companions/python/info.codefly.yaml | 2 +- docs/image-policy.md | 2 +- docs/runbooks/publish-companions.md | 21 ++++++++ internal/buildspecs/main.go | 66 ++++++++++++++++++++------ internal/buildspecs/main_test.go | 20 ++++++++ version/info.codefly.yaml | 2 +- 17 files changed, 132 insertions(+), 52 deletions(-) diff --git a/.github/workflows/companions-build.yml b/.github/workflows/companions-build.yml index f8369d4c3..25675a46c 100644 --- a/.github/workflows/companions-build.yml +++ b/.github/workflows/companions-build.yml @@ -32,6 +32,7 @@ jobs: plan: runs-on: ubuntu-latest outputs: + bake: ${{ steps.specs.outputs.bake }} companions: ${{ steps.specs.outputs.companions }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -40,7 +41,9 @@ jobs: go-version-file: go.mod cache: false - id: specs - run: echo "companions=$(go run ./internal/buildspecs)" >> "$GITHUB_OUTPUT" + run: | + echo "companions=$(go run ./internal/buildspecs)" >> "$GITHUB_OUTPUT" + echo "bake=$(go run ./internal/buildspecs --bake)" >> "$GITHUB_OUTPUT" build: needs: plan @@ -64,10 +67,10 @@ jobs: # Dockerfile that runs it during the build is exercised here too. # Publishing stages a binary cross-compiled from the CLI checkout # instead; this only has to be a codefly binary, not that one. - if: matrix.cli_binary != '' + if: matrix.cli_binary != '' || matrix.base_cli_binary != '' env: GH_TOKEN: ${{ github.token }} - CLI_BINARY: ${{ matrix.cli_binary }} + CLI_BINARY: ${{ matrix.cli_binary || matrix.base_cli_binary }} PLATFORMS: ${{ matrix.platforms }} run: | set -euo pipefail @@ -86,12 +89,17 @@ jobs: - uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0 - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + - name: Write the build graph + env: + BAKE: ${{ needs.plan.outputs.bake }} + run: printf '%s\n' "$BAKE" > "${{ runner.temp }}/companions.json" + - name: build - uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + uses: docker/bake-action@59cb1203cd42bcefd40a1b6d73b589e48c86040e # v7.5.0 with: - context: ${{ matrix.context }} - file: ${{ matrix.dockerfile }} - platforms: ${{ matrix.platforms }} - push: false - cache-from: type=gha,scope=companion-${{ matrix.companion }} - cache-to: type=gha,mode=max,scope=companion-${{ matrix.companion }} + source: . + files: ${{ runner.temp }}/companions.json + targets: ${{ matrix.companion }} + set: | + *.cache-from=type=gha,scope=companion-${{ matrix.companion }} + *.cache-to=type=gha,mode=max,scope=companion-${{ matrix.companion }} diff --git a/companions/codefly/Dockerfile b/companions/codefly/Dockerfile index 543eec5cf..b2d65e37f 100644 --- a/companions/codefly/Dockerfile +++ b/companions/codefly/Dockerfile @@ -1,17 +1,15 @@ -FROM alpine:3.24.2 +FROM alpine:3.24.2@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 # Base dev toolbox shared across every codefly-built image. Language # companions either FROM this image directly (when alpine suffices) or # `COPY --from=ghcr.io/codefly-dev/codefly` the codefly binary (when they need -# a language-specific base like python:3.13.1-alpine3.21). +# a language-specific base like Python on Alpine). # # Version pinning strategy: -# - Base image: pinned to alpine:3.21.3 (specific patch, not :latest). -# - apk packages: not individually pinned. Alpine point releases -# freeze their package repo, so `apk add git` from alpine:3.21.3 -# always yields the same git build. Pinning every apk line is -# brittle (versions shift between minor releases) and delivers no -# reproducibility benefit beyond what the base tag gives us. +# - Base image: Alpine 3.24.2, pinned by digest. +# - apk packages: the Alpine repository supplies current fixes at build +# time. Its packages are not frozen by the base tag; the published +# image digest identifies the resulting bytes. # - codefly CLI: copied from a locally-built binary, not a tag. # # Keep minimal. Heavy tools (language runtimes, LSP servers, proto diff --git a/companions/codefly/info.codefly.yaml b/companions/codefly/info.codefly.yaml index b1f07a6b4..888ec6e45 100644 --- a/companions/codefly/info.codefly.yaml +++ b/companions/codefly/info.codefly.yaml @@ -1 +1 @@ -version: 0.0.4 +version: 0.0.5 diff --git a/companions/go/Dockerfile b/companions/go/Dockerfile index c91bb927e..1d1a9a963 100644 --- a/companions/go/Dockerfile +++ b/companions/go/Dockerfile @@ -2,7 +2,7 @@ # companion image it builds on, and the builder resolves that to a registry. # The default keeps a plain `docker build` working and is pinned to the version # in companions/codefly/info.codefly.yaml. -ARG CODEFLY_BASE_IMAGE=ghcr.io/codefly-dev/codefly:0.0.4 +ARG CODEFLY_BASE_IMAGE=ghcr.io/codefly-dev/codefly:0.0.5 FROM ${CODEFLY_BASE_IMAGE} AS codeflybase FROM golang:1.27-alpine diff --git a/companions/go/info.codefly.yaml b/companions/go/info.codefly.yaml index 9555a667b..f9a0e57b9 100644 --- a/companions/go/info.codefly.yaml +++ b/companions/go/info.codefly.yaml @@ -1 +1 @@ -version: 0.0.11 +version: 0.0.12 diff --git a/companions/imagepolicy_test.go b/companions/imagepolicy_test.go index d44342ea1..8b26d8517 100644 --- a/companions/imagepolicy_test.go +++ b/companions/imagepolicy_test.go @@ -31,11 +31,9 @@ import ( // core release flow — so moving a base means republishing the companion. See // docs/runbooks/publish-companions.md. var unpinnedUpstreamBases = []string{ - "alpine:3.24.2", "docker:29-dind", "golang:1.27-alpine", "node:26.3.0-alpine3.22", - "python:3.14.1-alpine3.21", "rust:1.99-alpine", } diff --git a/companions/node/Dockerfile b/companions/node/Dockerfile index 646504387..0c8868c1b 100644 --- a/companions/node/Dockerfile +++ b/companions/node/Dockerfile @@ -10,7 +10,7 @@ # companion image it builds on, and the builder resolves that to a registry. # The default keeps a plain `docker build` working and is pinned to the version # in companions/codefly/info.codefly.yaml. -ARG CODEFLY_BASE_IMAGE=ghcr.io/codefly-dev/codefly:0.0.4 +ARG CODEFLY_BASE_IMAGE=ghcr.io/codefly-dev/codefly:0.0.5 FROM ${CODEFLY_BASE_IMAGE} AS codeflybase FROM node:26.3.0-alpine3.22 diff --git a/companions/node/info.codefly.yaml b/companions/node/info.codefly.yaml index d6e816cf6..282b91ba4 100644 --- a/companions/node/info.codefly.yaml +++ b/companions/node/info.codefly.yaml @@ -1 +1 @@ -version: 0.0.13 +version: 0.0.14 diff --git a/companions/proto/Dockerfile b/companions/proto/Dockerfile index ec64d162b..98ad57a61 100644 --- a/companions/proto/Dockerfile +++ b/companions/proto/Dockerfile @@ -16,7 +16,7 @@ # companion image it builds on, and the builder resolves that to a registry. # The default keeps a plain `docker build` working and is pinned to the version # in companions/codefly/info.codefly.yaml. -ARG CODEFLY_BASE_IMAGE=ghcr.io/codefly-dev/codefly:0.0.4 +ARG CODEFLY_BASE_IMAGE=ghcr.io/codefly-dev/codefly:0.0.5 # --- Builder stage: compile every Go plugin from source -------------- diff --git a/companions/proto/info.codefly.yaml b/companions/proto/info.codefly.yaml index 8c04ba083..1be3e0519 100644 --- a/companions/proto/info.codefly.yaml +++ b/companions/proto/info.codefly.yaml @@ -1 +1 @@ -version: 0.0.18 +version: 0.0.19 diff --git a/companions/python/Dockerfile b/companions/python/Dockerfile index 490fad1cf..2e3593ee7 100644 --- a/companions/python/Dockerfile +++ b/companions/python/Dockerfile @@ -1,11 +1,10 @@ # Python runtime companion — opinionated, uv-based (no poetry). # # Version pinning strategy (matches codefly base): -# - Base image: python:3.13.1-alpine3.21 — pins both Python patch and -# Alpine minor release. -# - apk packages: not individually pinned — Alpine point release -# freezes its repo, so `apk add git` yields the same version at -# every build. +# - Base image: Python 3.14.10 on Alpine 3.24, pinned by digest. +# - apk packages: not individually pinned — the Alpine repository +# supplies current fixes at build time. The published image digest, +# rather than the package repository, identifies the resulting bytes. # - uv: pinned via the install-script URL (version is part of the # path), bumping = editing this line + bumping info.codefly.yaml. # - codefly CLI: copied from the pinned codefly companion base. @@ -14,10 +13,10 @@ # companion image it builds on, and the builder resolves that to a registry. # The default keeps a plain `docker build` working and is pinned to the version # in companions/codefly/info.codefly.yaml. -ARG CODEFLY_BASE_IMAGE=ghcr.io/codefly-dev/codefly:0.0.4 +ARG CODEFLY_BASE_IMAGE=ghcr.io/codefly-dev/codefly:0.0.5 FROM ${CODEFLY_BASE_IMAGE} AS codeflybase -FROM python:3.14.1-alpine3.21 +FROM python:3.14.10-alpine3.24@sha256:cfe4b5079e5852da965ae14f74a6e2b137ece05b918d27c575fb70de3c85db2b # Re-install alpine dev tools so the python companion's runtime carries # the same toolbox as the codefly base. Dropped from the list: diff --git a/companions/python/info.codefly.yaml b/companions/python/info.codefly.yaml index 888ec6e45..5c0a9fa32 100644 --- a/companions/python/info.codefly.yaml +++ b/companions/python/info.codefly.yaml @@ -1 +1 @@ -version: 0.0.5 +version: 0.0.6 diff --git a/docs/image-policy.md b/docs/image-policy.md index 1be48cc36..f2807b5e5 100644 --- a/docs/image-policy.md +++ b/docs/image-policy.md @@ -72,7 +72,7 @@ The companion base-image argument's tag default is exempted only from the unpinned-base refusal: the publisher overrides it with the digest it just published. Existing companion build-spec tests enforce that default's shape. -Six distinct floating upstream bases remain explicitly listed in +Four distinct floating upstream bases remain explicitly listed in `unpinnedUpstreamBases`. This is a **temporary stopgap**, not completed pinning: new references fail and stale exceptions fail once no companion uses them. Pinning them requires the companion build/publish contract in diff --git a/docs/runbooks/publish-companions.md b/docs/runbooks/publish-companions.md index bb5d4fc20..102964b49 100644 --- a/docs/runbooks/publish-companions.md +++ b/docs/runbooks/publish-companions.md @@ -121,3 +121,24 @@ curl -sI -H "Authorization: Bearer $token" \ Checklist — do this once per package: `codefly`, `execution`, `go`, `node`, `proto`, `python`. + +## October 2026 image refresh + +Core 0.19.11 selects codefly 0.0.5 and Python 0.0.6. Python now uses +Python 3.14.10 on Alpine 3.24; both it and the codefly Alpine 3.24.2 base +are digest-pinned. The go 0.0.12, node 0.0.14 and proto 0.0.19 companions +also move to the refreshed codefly base. Published tags are not overwritten. +Alpine package repositories supply current fixes during the build; a base +version does not freeze the packages installed by `apk`. + +The PR build derives a Buildx Bake graph from `companions.BuildSpecs()`. +Each dependent uses a `target:` context for its declared base, so it tests +the base from the same checkout before that new tag is published. CI stages +the released CLI executable for this check; the CLI publishing workflow +builds its own binary from source, as described above. + +After publishing, record the actual image digests and scan those images. +Consumers must adopt the new Core/agent selections and rebuild their service +images; a source merge or a successful tag-existence check does not refresh +an already published service image. NYL acceptance requires its re-scan of +those resulting images, including vulnerability and secret policies. diff --git a/internal/buildspecs/main.go b/internal/buildspecs/main.go index 9468dadc0..8def18c4c 100644 --- a/internal/buildspecs/main.go +++ b/internal/buildspecs/main.go @@ -6,6 +6,7 @@ package main import ( "encoding/json" + "flag" "fmt" "os" "strings" @@ -16,41 +17,76 @@ import ( // entry is one companion rendered as the flat, string-valued shape a GitHub // Actions matrix entry and a reusable workflow's inputs can carry. type entry struct { - Companion string `json:"companion"` - Version string `json:"version"` - Dockerfile string `json:"dockerfile"` - Context string `json:"context"` - Platforms string `json:"platforms"` - CLIBinary string `json:"cli_binary"` - Base string `json:"base"` + Companion string `json:"companion"` + Version string `json:"version"` + Dockerfile string `json:"dockerfile"` + Context string `json:"context"` + Platforms string `json:"platforms"` + CLIBinary string `json:"cli_binary"` + Base string `json:"base"` + BaseCLIBinary string `json:"base_cli_binary"` } func entries(specs []companions.BuildSpec) []entry { + cliBinaries := make(map[string]string, len(specs)) + for _, spec := range specs { + cliBinaries[spec.Name] = spec.CLIBinary + } out := make([]entry, 0, len(specs)) for _, spec := range specs { out = append(out, entry{ - Companion: spec.Name, - Version: spec.Version, - Dockerfile: spec.Dockerfile, - Context: spec.Context, - Platforms: strings.Join(spec.Platforms, ","), - CLIBinary: spec.CLIBinary, - Base: spec.Base, + Companion: spec.Name, + Version: spec.Version, + Dockerfile: spec.Dockerfile, + Context: spec.Context, + Platforms: strings.Join(spec.Platforms, ","), + CLIBinary: spec.CLIBinary, + Base: spec.Base, + BaseCLIBinary: cliBinaries[spec.Base], }) } return out } func main() { + bake := flag.Bool("bake", false, "emit a Buildx Bake graph with companion bases built from this checkout") + flag.Parse() specs, err := companions.BuildSpecs() if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } - encoded, err := json.Marshal(entries(specs)) + var output any = entries(specs) + if *bake { + output = bakeFile(specs) + } + encoded, err := json.Marshal(output) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } fmt.Println(string(encoded)) } + +type bakeTarget struct { + Context string `json:"context"` + Dockerfile string `json:"dockerfile"` + Platforms []string `json:"platforms"` + Contexts map[string]string `json:"contexts,omitempty"` + Args map[string]string `json:"args,omitempty"` +} + +// A target context builds the declared base from the same checkout. A release +// PR can therefore qualify a new base before its tag exists in the registry. +func bakeFile(specs []companions.BuildSpec) map[string]map[string]bakeTarget { + targets := make(map[string]bakeTarget, len(specs)) + for _, spec := range specs { + target := bakeTarget{Context: spec.Context, Dockerfile: spec.Dockerfile, Platforms: spec.Platforms} + if spec.Base != "" { + target.Contexts = map[string]string{spec.Base: "target:" + spec.Base} + target.Args = map[string]string{companions.BaseImageArg: spec.Base} + } + targets[spec.Name] = target + } + return map[string]map[string]bakeTarget{"target": targets} +} diff --git a/internal/buildspecs/main_test.go b/internal/buildspecs/main_test.go index cb1362742..113f4d80e 100644 --- a/internal/buildspecs/main_test.go +++ b/internal/buildspecs/main_test.go @@ -37,3 +37,23 @@ func TestPlatformsJoinAsBuildxAcceptsThem(t *testing.T) { require.NotContains(t, e.Platforms, " ") } } + +// A dependent must build its checkout's base, not pull the unpublished tag. +// The matrix also needs that base's real CLI executable staged before baking. +func TestBakeDependenciesHaveLocalTargetsAndStagedBinaries(t *testing.T) { + specs, err := companions.BuildSpecs() + require.NoError(t, err) + targets := bakeFile(specs)["target"] + for _, entry := range entries(specs) { + target := targets[entry.Companion] + if entry.Base == "" { + require.Empty(t, target.Contexts) + continue + } + base, ok := targets[entry.Base] + require.True(t, ok, "base must be in the same build graph") + require.Equal(t, base.Platforms, target.Platforms) + require.Equal(t, "target:"+entry.Base, target.Contexts[target.Args[companions.BaseImageArg]]) + require.NotEmpty(t, entry.BaseCLIBinary) + } +} diff --git a/version/info.codefly.yaml b/version/info.codefly.yaml index 578f5f326..319d44d91 100644 --- a/version/info.codefly.yaml +++ b/version/info.codefly.yaml @@ -1 +1 @@ -version: 0.19.10 +version: 0.19.11 From 5c2bc78edb803e3d955e58e1ea8063b61ffb446a Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Wed, 28 Oct 2026 12:01:35 -0400 Subject: [PATCH 3/5] fix(proto): preserve generator compatibility on the refreshed runtime --- companions/proto/Dockerfile | 14 ++++++++++---- companions/proto/README.md | 5 ++++- companions/proto/companion_plugins_test.go | 8 ++++---- docs/runbooks/bump-protobuf-es.md | 2 +- docs/runbooks/publish-companions.md | 3 ++- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/companions/proto/Dockerfile b/companions/proto/Dockerfile index 98ad57a61..f4f4b6277 100644 --- a/companions/proto/Dockerfile +++ b/companions/proto/Dockerfile @@ -17,6 +17,7 @@ # The default keeps a plain `docker build` working and is pinned to the version # in companions/codefly/info.codefly.yaml. ARG CODEFLY_BASE_IMAGE=ghcr.io/codefly-dev/codefly:0.0.5 +FROM ${CODEFLY_BASE_IMAGE} AS codeflybase # --- Builder stage: compile every Go plugin from source -------------- @@ -56,7 +57,12 @@ RUN apk add --no-cache musl-dev && \ cargo install --locked protoc-gen-prost --version 0.5.0 && \ cargo install --locked protoc-gen-tonic --version 0.5.0 -FROM ${CODEFLY_BASE_IMAGE} +# Keep the interpreter compatible with the pinned generator's wheels and +# gencode contract. Alpine's python3 moves independently; grpcio-tools 1.71 +# has Python 3.13 wheels, not 3.14 wheels. As in the Python companion, copy +# the freshly built CLI into a language-specific runtime base. +FROM python:3.13.18-alpine3.24@sha256:bbc0290146ebe0a8b9a8ed07d3f2c7bdeb4d917fc93300a4e47490c8b5a11b48 +COPY --from=codeflybase /bin/codefly /bin/codefly # Runtime dependencies: # - nodejs + npm: for the TypeScript generators below (unavoidable). @@ -87,11 +93,11 @@ 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 — the builder's alpine ships a different python minor version, and a +# Python 3.13 — the builder may ship a different Python minor version, and a # venv is not portable across them. -RUN apk add --no-cache nodejs npm protobuf python3 py3-pip && \ +RUN apk add --no-cache bash ca-certificates curl git jq procps tzdata nodejs npm protobuf && \ python3 -m venv /venv && \ - /venv/bin/pip install --no-cache-dir protobuf==5.29.3 grpcio-tools==1.71.0 && \ + /venv/bin/pip install --no-cache-dir --only-binary=:all: protobuf==5.29.3 grpcio-tools==1.71.0 && \ npm install -g \ openapi-typescript@7.13.0 \ swagger2openapi@7.0.8 \ diff --git a/companions/proto/README.md b/companions/proto/README.md index 9886df824..94858a07f 100644 --- a/companions/proto/README.md +++ b/companions/proto/README.md @@ -120,7 +120,10 @@ Python bindings come from the companion's own plugin, `protoc-gen-codefly-python (`facades/python/protoc_gen_codefly_python.py`), as Go's and Rust's come from local plugins: it compiles the `*_pb2` messages and `*_pb2_grpc` stubs with the `grpc_tools` pinned in the image's venv (`grpcio-tools==1.71.0`, -`protobuf==5.29.3`), so the gencode is versioned with the image. buf's remote +`protobuf==5.29.3`), so the gencode is versioned with the image. The Docker +runtime uses digest-pinned Python 3.13.18 on Alpine 3.24: these generator pins +have Python 3.13 wheels, and moving the generator to support a newer interpreter +would also change the generated-code runtime requirements. buf's remote `protocolbuffers/python` and `grpc/python` plugins it replaces moved on their own, and their gencode outran the protobuf runtime a generated library declares. It ships in companion version 0.0.17. diff --git a/companions/proto/companion_plugins_test.go b/companions/proto/companion_plugins_test.go index a18add35d..c39abc045 100644 --- a/companions/proto/companion_plugins_test.go +++ b/companions/proto/companion_plugins_test.go @@ -326,10 +326,10 @@ 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:98": "@bufbuild/protoc-gen-es@", - "flake.nix:51": "protocGenEsVersion =", - "flake.nix:59": "hash =", - "flake.nix:61": "npmDepsHash =", + "Dockerfile:104": "@bufbuild/protoc-gen-es@", + "flake.nix:51": "protocGenEsVersion =", + "flake.nix:59": "hash =", + "flake.nix:61": "npmDepsHash =", } func TestRunbookCitationsPointAtWhatTheyName(t *testing.T) { diff --git a/docs/runbooks/bump-protobuf-es.md b/docs/runbooks/bump-protobuf-es.md index d91e05d34..3bdc03dfe 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:98` | `@bufbuild/protoc-gen-es@` installed via npm | yes | +| `companions/proto/Dockerfile:104` | `@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/docs/runbooks/publish-companions.md b/docs/runbooks/publish-companions.md index 102964b49..0387e1c75 100644 --- a/docs/runbooks/publish-companions.md +++ b/docs/runbooks/publish-companions.md @@ -127,7 +127,8 @@ Checklist — do this once per package: `codefly`, `execution`, `go`, `node`, Core 0.19.11 selects codefly 0.0.5 and Python 0.0.6. Python now uses Python 3.14.10 on Alpine 3.24; both it and the codefly Alpine 3.24.2 base are digest-pinned. The go 0.0.12, node 0.0.14 and proto 0.0.19 companions -also move to the refreshed codefly base. Published tags are not overwritten. +also carry the refreshed codefly CLI. Proto retains its generator versions +on a digest-pinned Python 3.13.18 runtime compatible with their wheels. Published tags are not overwritten. Alpine package repositories supply current fixes during the build; a base version does not freeze the packages installed by `apk`. From cdb7f459e9cc568d6f8784a598f9fffedebaf424 Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:42:06 -0400 Subject: [PATCH 4/5] fix(deps): refresh vulnerable Go dependencies and companion versions --- companions/codefly/info.codefly.yaml | 2 +- companions/execution/info.codefly.yaml | 2 +- companions/go/Dockerfile | 2 +- companions/go/info.codefly.yaml | 2 +- companions/node/Dockerfile | 2 +- companions/node/info.codefly.yaml | 2 +- companions/proto/Dockerfile | 2 +- companions/proto/info.codefly.yaml | 2 +- companions/python/Dockerfile | 2 +- companions/python/info.codefly.yaml | 2 +- docs/runbooks/publish-companions.md | 15 +++++++ go.mod | 30 ++++++------- go.sum | 58 +++++++++++--------------- version/info.codefly.yaml | 2 +- 14 files changed, 62 insertions(+), 63 deletions(-) diff --git a/companions/codefly/info.codefly.yaml b/companions/codefly/info.codefly.yaml index 888ec6e45..5c0a9fa32 100644 --- a/companions/codefly/info.codefly.yaml +++ b/companions/codefly/info.codefly.yaml @@ -1 +1 @@ -version: 0.0.5 +version: 0.0.6 diff --git a/companions/execution/info.codefly.yaml b/companions/execution/info.codefly.yaml index b1465aaf8..af0337af5 100644 --- a/companions/execution/info.codefly.yaml +++ b/companions/execution/info.codefly.yaml @@ -1 +1 @@ -version: 0.0.1 +version: 0.0.2 diff --git a/companions/go/Dockerfile b/companions/go/Dockerfile index 1d1a9a963..bd40d6b59 100644 --- a/companions/go/Dockerfile +++ b/companions/go/Dockerfile @@ -2,7 +2,7 @@ # companion image it builds on, and the builder resolves that to a registry. # The default keeps a plain `docker build` working and is pinned to the version # in companions/codefly/info.codefly.yaml. -ARG CODEFLY_BASE_IMAGE=ghcr.io/codefly-dev/codefly:0.0.5 +ARG CODEFLY_BASE_IMAGE=ghcr.io/codefly-dev/codefly:0.0.6 FROM ${CODEFLY_BASE_IMAGE} AS codeflybase FROM golang:1.27-alpine diff --git a/companions/go/info.codefly.yaml b/companions/go/info.codefly.yaml index f9a0e57b9..d6e816cf6 100644 --- a/companions/go/info.codefly.yaml +++ b/companions/go/info.codefly.yaml @@ -1 +1 @@ -version: 0.0.12 +version: 0.0.13 diff --git a/companions/node/Dockerfile b/companions/node/Dockerfile index 0c8868c1b..3364f8cfc 100644 --- a/companions/node/Dockerfile +++ b/companions/node/Dockerfile @@ -10,7 +10,7 @@ # companion image it builds on, and the builder resolves that to a registry. # The default keeps a plain `docker build` working and is pinned to the version # in companions/codefly/info.codefly.yaml. -ARG CODEFLY_BASE_IMAGE=ghcr.io/codefly-dev/codefly:0.0.5 +ARG CODEFLY_BASE_IMAGE=ghcr.io/codefly-dev/codefly:0.0.6 FROM ${CODEFLY_BASE_IMAGE} AS codeflybase FROM node:26.3.0-alpine3.22 diff --git a/companions/node/info.codefly.yaml b/companions/node/info.codefly.yaml index 282b91ba4..8ffeae728 100644 --- a/companions/node/info.codefly.yaml +++ b/companions/node/info.codefly.yaml @@ -1 +1 @@ -version: 0.0.14 +version: 0.0.15 diff --git a/companions/proto/Dockerfile b/companions/proto/Dockerfile index f4f4b6277..3ab37c516 100644 --- a/companions/proto/Dockerfile +++ b/companions/proto/Dockerfile @@ -16,7 +16,7 @@ # companion image it builds on, and the builder resolves that to a registry. # The default keeps a plain `docker build` working and is pinned to the version # in companions/codefly/info.codefly.yaml. -ARG CODEFLY_BASE_IMAGE=ghcr.io/codefly-dev/codefly:0.0.5 +ARG CODEFLY_BASE_IMAGE=ghcr.io/codefly-dev/codefly:0.0.6 FROM ${CODEFLY_BASE_IMAGE} AS codeflybase # --- Builder stage: compile every Go plugin from source -------------- diff --git a/companions/proto/info.codefly.yaml b/companions/proto/info.codefly.yaml index 1be3e0519..89acbc43c 100644 --- a/companions/proto/info.codefly.yaml +++ b/companions/proto/info.codefly.yaml @@ -1 +1 @@ -version: 0.0.19 +version: 0.0.20 diff --git a/companions/python/Dockerfile b/companions/python/Dockerfile index 2e3593ee7..a19e82afc 100644 --- a/companions/python/Dockerfile +++ b/companions/python/Dockerfile @@ -13,7 +13,7 @@ # companion image it builds on, and the builder resolves that to a registry. # The default keeps a plain `docker build` working and is pinned to the version # in companions/codefly/info.codefly.yaml. -ARG CODEFLY_BASE_IMAGE=ghcr.io/codefly-dev/codefly:0.0.5 +ARG CODEFLY_BASE_IMAGE=ghcr.io/codefly-dev/codefly:0.0.6 FROM ${CODEFLY_BASE_IMAGE} AS codeflybase FROM python:3.14.10-alpine3.24@sha256:cfe4b5079e5852da965ae14f74a6e2b137ece05b918d27c575fb70de3c85db2b diff --git a/companions/python/info.codefly.yaml b/companions/python/info.codefly.yaml index 5c0a9fa32..87fb744f1 100644 --- a/companions/python/info.codefly.yaml +++ b/companions/python/info.codefly.yaml @@ -1 +1 @@ -version: 0.0.6 +version: 0.0.7 diff --git a/docs/runbooks/publish-companions.md b/docs/runbooks/publish-companions.md index 0387e1c75..72f09af8b 100644 --- a/docs/runbooks/publish-companions.md +++ b/docs/runbooks/publish-companions.md @@ -143,3 +143,18 @@ Consumers must adopt the new Core/agent selections and rebuild their service images; a source merge or a successful tag-existence check does not refresh an already published service image. NYL acceptance requires its re-scan of those resulting images, including vulnerability and secret policies. + +## Follow-up dependency refresh + +The scan of the published codefly 0.0.5 binary found GO-2026-6860 and +GO-2026-6866 in `golang.org/x/net` 0.61.0; both are fixed in 0.62.0. +Core 0.19.12 raises that dependency and selects new companion versions: +codefly 0.0.6, Python 0.0.7, Go 0.0.13, Node 0.0.15, proto 0.0.20 and +execution 0.0.2. Every companion carries the CLI, so every image needs a +new version even where its Dockerfile otherwise stays the same. + +Publish this set only after the CLI dependency refresh is merged: the +publisher compiles the CLI checkout, and a Core dependency bump alone does +not change that executable. Verify the published executable's module +versions and scan it again before recording the fixes as delivered. Keep +previous tags immutable, and adopt the new Python index digest in FastAPI. diff --git a/go.mod b/go.mod index a54a269ff..aee98297d 100644 --- a/go.mod +++ b/go.mod @@ -34,9 +34,8 @@ require ( github.com/google/go-cmp v0.7.0 github.com/google/go-github/v89 v89.0.0 github.com/google/uuid v1.6.0 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.32.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.33.0 github.com/hashicorp/go-multierror v1.1.1 - github.com/mitchellh/mapstructure v1.5.0 github.com/moby/buildkit v0.34.1 github.com/moby/patternmatcher v0.6.1 github.com/muesli/termenv v0.16.0 @@ -69,18 +68,18 @@ require ( go.opentelemetry.io/otel/trace v1.47.0 go.uber.org/goleak v1.3.0 golang.org/x/mod v0.42.0 - golang.org/x/net v0.61.0 + golang.org/x/net v0.62.0 golang.org/x/sys v0.49.0 golang.org/x/term v0.47.0 golang.org/x/text v0.43.0 - google.golang.org/genproto/googleapis/api v0.0.0-20261012220534-4341edf3f941 - google.golang.org/genproto/googleapis/rpc v0.0.0-20261005182115-fad411399dd8 - google.golang.org/grpc v1.84.0 + google.golang.org/genproto/googleapis/api v0.0.0-20261026165753-2ffab32bce7e + google.golang.org/genproto/googleapis/rpc v0.0.0-20261026165753-2ffab32bce7e + google.golang.org/grpc v1.85.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 mvdan.cc/sh/v3 v3.14.1 - sigs.k8s.io/kustomize/api v0.21.1 - sigs.k8s.io/kustomize/kyaml v0.21.1 + sigs.k8s.io/kustomize/api v0.21.3 + sigs.k8s.io/kustomize/kyaml v0.21.3 ) require ( @@ -109,7 +108,6 @@ require ( github.com/cloudflare/circl v1.6.3 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/typeurl/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect github.com/dlclark/regexp2 v1.12.0 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -123,19 +121,15 @@ require ( github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-openapi/analysis v1.0.0 // indirect + github.com/go-openapi/analysis v1.0.1 // indirect github.com/go-openapi/errors v0.22.9 // indirect - github.com/go-openapi/jsonpointer v1.0.1 // indirect - github.com/go-openapi/jsonreference v1.0.2 // indirect - github.com/go-openapi/strfmt v0.27.3 // indirect - github.com/go-openapi/swag v0.29.2 // indirect - github.com/go-openapi/swag/cmdutils v0.29.2 // indirect + github.com/go-openapi/jsonpointer v1.0.2 // indirect + github.com/go-openapi/jsonreference v1.0.3 // indirect + github.com/go-openapi/strfmt v0.27.4 // indirect github.com/go-openapi/swag/conv v0.29.2 // indirect - github.com/go-openapi/swag/fileutils v0.29.2 // indirect github.com/go-openapi/swag/jsonutils v0.29.2 // indirect github.com/go-openapi/swag/loading v0.29.2 // indirect github.com/go-openapi/swag/mangling v0.29.2 // indirect - github.com/go-openapi/swag/netutils v0.29.2 // indirect github.com/go-openapi/swag/pools v0.29.2 // indirect github.com/go-openapi/swag/stringutils v0.29.2 // indirect github.com/go-openapi/swag/typeutils v0.29.2 // indirect @@ -199,7 +193,7 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gotest.tools/v3 v3.5.1 // indirect - k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect + k8s.io/kube-openapi v0.0.0-20261001230523-97fa35140926 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 3d4d0084f..2dd7d9bce 100644 --- a/go.sum +++ b/go.sum @@ -102,7 +102,6 @@ github.com/creativeprojects/go-selfupdate v1.7.0/go.mod h1:RVszRkQ0kVQB2hbqrsgrE github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE= github.com/cyphar/filepath-securejoin v0.7.0/go.mod h1:ymLGms/u3BYaviIiuKFnUx8EkQEZeK6cInNoAPJA3o4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= @@ -152,28 +151,23 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-openapi/analysis v1.0.0 h1:sNvbAGCJqUTqIAodr9IVqJMmuZas3YS9ms1dGK9yiJ4= -github.com/go-openapi/analysis v1.0.0/go.mod h1:NhYjJ57fnE+bcE7UwrJyMkhWA3Dfz7TdiBfTVAnos4Y= +github.com/go-openapi/analysis v1.0.1 h1:JTEyujgl4GyFj8pbMUY2yZx5ak1Ozt/DaqzQjQBnBWM= +github.com/go-openapi/analysis v1.0.1/go.mod h1:f3gYbXmnXSQIZB02DqE4f8JX969PdOEnaY7gDBLeJjI= github.com/go-openapi/errors v0.22.9 h1:HI9+SyVYiRzyeBQGv0CbWbQa+u2S0nny7G7AhY+UMiM= github.com/go-openapi/errors v0.22.9/go.mod h1:R73q66sXP2smzAGgFOI4nqtusRK2xN5pYCjsij+KX58= -github.com/go-openapi/jsonpointer v1.0.1 h1:2KxywRmNwJkT/FMBa3iRNHEaAxSJvjqoufQZy3au1Mg= -github.com/go-openapi/jsonpointer v1.0.1/go.mod h1:wI7ZYsFmbIi9nBXOZqgDaS/bqOchRGZjqxFli7FBYxY= -github.com/go-openapi/jsonreference v1.0.2 h1:oS4et8FOf3p3UQxEo4Xt0esijmBUM+F259Xl72OSZsc= -github.com/go-openapi/jsonreference v1.0.2/go.mod h1:TbUNSOo+fcorZjFaNoiSDSoaNnnZtqJtLGR1PuvE/Cs= +github.com/go-openapi/jsonpointer v1.0.2 h1:NbW7xT2b+l7x+JefiPGDsZWsrLk1ogrz4AHK1TTzMt8= +github.com/go-openapi/jsonpointer v1.0.2/go.mod h1:N0rI4MbNKD7xAFTiHtB+5Nhrg2j/wwYg7m7Fmb4/jk8= +github.com/go-openapi/jsonreference v1.0.3 h1:5rDbq+JsjCv6/nUDDJt83nLZoADI4w03uvHXer+4aYQ= +github.com/go-openapi/jsonreference v1.0.3/go.mod h1:OJSlMXHysgH272D9z2tiy2MGe5A9vQLHaCPb6KbYdE4= github.com/go-openapi/loads v0.25.3 h1:V+jKy/thXWdLJUuYC8sZX2dICyAa8M3DokF70jj34P0= github.com/go-openapi/loads v0.25.3/go.mod h1:LgLyCSOLBL2Qnj0Ps1oo2YH8jZoEKsMeVZCc+ALIxFo= github.com/go-openapi/spec v1.0.1 h1:lj2vdGpNDcVgwRc6qXdw6qt/KQpCtSa9tnUH6vpDPDk= github.com/go-openapi/spec v1.0.1/go.mod h1:M//GWQGtDUAjnP37gE6fInLgaczB+FatoipV3H1fYw8= -github.com/go-openapi/strfmt v0.27.3 h1:cyf4J5Wjpd6bFpAs44MF8PsbWF/mWXhn82Ck/hgQZgw= -github.com/go-openapi/strfmt v0.27.3/go.mod h1:Bl+xadPLa3nhBiaCAxNND1vrQfzyEjF0vBrLx96n/4E= +github.com/go-openapi/strfmt v0.27.4 h1:E1xsvHb8R9vsuYru4TP2PVVTv9AIgSFAUACUZrFQfCI= +github.com/go-openapi/strfmt v0.27.4/go.mod h1:SCOff81PPhU26acBiW96jHaFycEbT2nkQRvojM+V2z0= github.com/go-openapi/swag v0.29.2 h1:9n8frkcsuQRA0INU31VbxNxt1zBaHxrHYGEf5BUjFi4= -github.com/go-openapi/swag v0.29.2/go.mod h1:RkCiX1gCVXWgLOavDXE3ALJrQ/VO2ziiIzOUs3z10xo= -github.com/go-openapi/swag/cmdutils v0.29.2 h1:cXEzX/nWCODon251f+1HgL75/R4XXML5IZxgLCWMoro= -github.com/go-openapi/swag/cmdutils v0.29.2/go.mod h1:XziaSVzYqkDvpWXzASfWajw2YvJrLscq610O2XiyqJw= github.com/go-openapi/swag/conv v0.29.2 h1:8c9shoB8l0QRSR6ymq1llHdBWDqpIgJfjTGHx3Vuhm0= github.com/go-openapi/swag/conv v0.29.2/go.mod h1:AZS0YigTNf8qNtD6WqJz/N4RUNmpr76SyjFGkq4+p6Q= -github.com/go-openapi/swag/fileutils v0.29.2 h1:mdUL+Vw5ah1fO1AvFCoHeIyv7YZsCz2MN3KNqd/lLVI= -github.com/go-openapi/swag/fileutils v0.29.2/go.mod h1:7QKmmodjAebaq61lGVJa8ldLdGeMF4DSgYv8tXOGGo0= github.com/go-openapi/swag/jsonutils v0.29.2 h1:uZNSD2/rJDYAfvsLYkykTzXuyxM3QpDKV9jhRHrMu6k= github.com/go-openapi/swag/jsonutils v0.29.2/go.mod h1:ONTdNvj3Y+IXRzfa17E+Rgt2ns/qiSOYjIo2K7v2yDs= github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.2 h1:w+Fd6EBOMGlC74GYGHqCLGyb3Bpams8TtNrgM/SG4jo= @@ -182,8 +176,6 @@ github.com/go-openapi/swag/loading v0.29.2 h1:QU1ry24e6r6Shoxe380Nx0X5iKkpRO+hJf github.com/go-openapi/swag/loading v0.29.2/go.mod h1:DqilDjuiJKETecsS4TRopWG1acUDsfqN39ZmQhPu6uI= github.com/go-openapi/swag/mangling v0.29.2 h1:ltdo3K0tTP4P6zWhVVWDU9D37/YYBuMJRdZxiS2Dtog= github.com/go-openapi/swag/mangling v0.29.2/go.mod h1:RjDi4TnItAenS3cgCXSpIl1kKbjG7il6pnmQVl/8bp8= -github.com/go-openapi/swag/netutils v0.29.2 h1:dBli6jyUa93sDS/XYWShJzf4+nnVfFKfvY9SLUaUzB8= -github.com/go-openapi/swag/netutils v0.29.2/go.mod h1:1pM6Xg/Um8E1eps3umWl8Hme3ByWXOtHsvFC0CULHUE= github.com/go-openapi/swag/pools v0.29.2 h1:PlGoDRF8WtSyXkedZZkpW3f9wDhFf3u8KtaBcmgmh8c= github.com/go-openapi/swag/pools v0.29.2/go.mod h1:V3lhxVT4qDYRqc2MRSSbLsTYIYV/2HlHafhyEkmzLIc= github.com/go-openapi/swag/stringutils v0.29.2 h1:lcnBxwAaysT3bMUVBfn9V/PkfVqkurpqufKU6rTUMGk= @@ -223,8 +215,8 @@ github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/graph-gophers/graphql-go v1.9.0 h1:yu0ucKHLc5qGpRwLYKIWtr9bOoxovkWasuBrPQwlHls= github.com/graph-gophers/graphql-go v1.9.0/go.mod h1:23olKZ7duEvHlF/2ELEoSZaY1aNPfShjP782SOoNTyM= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.32.0 h1:QMILzF8BA7xftE4zVORkRrlv08m12St3csK4RsyRgRs= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.32.0/go.mod h1:+ZQ6iUL8+ATyxGDQNuRKpNwJFdGDUi0e7E1QL9MvI7U= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.33.0 h1:5T5Ns3kMpWr+z3s5ZA0e7ljVyWNVNxK76sHPAxi5D1k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.33.0/go.mod h1:oWNYw09cH8wl8fFgC5hKlBLxR4XfKy2KQpP8MJjeCcc= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -268,8 +260,6 @@ github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhV github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/buildkit v0.34.1 h1:jVdxsXX0UpU0q1Et+9lFezPmh9sGE6vF0TrKZaGlfy4= github.com/moby/buildkit v0.34.1/go.mod h1:e+Za70y2a3nL1U2Y0P8TvhPyKp2Ok2NS+Q4siSArDiM= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -450,8 +440,8 @@ golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw= -golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI= +golang.org/x/net v0.62.0 h1:18ELalfM5+XNRwzM2nYqXsZQ9ByS1uhj2O/U7+5jqRY= +golang.org/x/net v0.62.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA= @@ -483,12 +473,12 @@ golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20261012220534-4341edf3f941 h1:DS/w3N8rsfrwxwgGSj6WhMsPo7aNQGmDx74J81zd0NQ= -google.golang.org/genproto/googleapis/api v0.0.0-20261012220534-4341edf3f941/go.mod h1:AuGAdi9KnXss7xbLxIKhCvk+Ro/SxpET/iS45DNYN+M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20261005182115-fad411399dd8 h1:0SXBqli5dnuCKMjI/X3FM226zEaEa0GUcSCJtN1vL2o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20261005182115-fad411399dd8/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= -google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= -google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= +google.golang.org/genproto/googleapis/api v0.0.0-20261026165753-2ffab32bce7e h1:e789sLZjeTdc02ZCQGTSzHOu0fR73+dwPQVKUp67Z88= +google.golang.org/genproto/googleapis/api v0.0.0-20261026165753-2ffab32bce7e/go.mod h1:vHV3wJM9WZyfV7GyB+OQGq5dWVrFwCDxapPK4WlRD88= +google.golang.org/genproto/googleapis/rpc v0.0.0-20261026165753-2ffab32bce7e h1:w3fy/G8OrJHYexn+u5y7hFyc8YRRIr7DCevtIOwAcGY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20261026165753-2ffab32bce7e/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.85.0 h1:BPBCfuVRQ1qvBhKDOq9uvQllMvO59rKsYhXpp7Dd8vw= +google.golang.org/grpc v1.85.0/go.mod h1:0rQrOD58yjal/0EaVbGBANindixsCZNQPDV3VKtaKuQ= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -506,16 +496,16 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A= -k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= +k8s.io/kube-openapi v0.0.0-20261001230523-97fa35140926 h1:Fe0G8t8FaMDn2lpat0IHu3X9lHrVyGzDxhhCwKXKp28= +k8s.io/kube-openapi v0.0.0-20261001230523-97fa35140926/go.mod h1:3e2VxsK2svhjvkNik92kYU1a5kgau5tZeAuEWwoN4q0= mvdan.cc/sh/v3 v3.14.1 h1:bXkhQWNHCs0KZEChF8hYS6FC+T2N9mUZLbQv9blditI= mvdan.cc/sh/v3 v3.14.1/go.mod h1:syYCoFET8w9tvevxiXUtY8/ICrU+l26jHmhJDra3Vwo= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= -sigs.k8s.io/kustomize/api v0.21.1/go.mod h1:f3wkKByTrgpgltLgySCntrYoq5d3q7aaxveSagwTlwI= -sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI= -sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= +sigs.k8s.io/kustomize/api v0.21.3 h1:DXGrbifGrAd+7AQ75Kx/tVhr+RLMCZFfAYl61HOR3T0= +sigs.k8s.io/kustomize/api v0.21.3/go.mod h1:JKlHjHXGh54cdoWVdKNqDscFzPb9z+sBk9upy9PBh6k= +sigs.k8s.io/kustomize/kyaml v0.21.3 h1:4ygzLsc8B1ep9dFuEivrJ2lFIAglL19dhPrWp1ioceY= +sigs.k8s.io/kustomize/kyaml v0.21.3/go.mod h1:HrAVNlYyEphOlRCcaSIQziGR+0ehwx6Xp1ucUrpC6hs= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= diff --git a/version/info.codefly.yaml b/version/info.codefly.yaml index 319d44d91..9469d9d18 100644 --- a/version/info.codefly.yaml +++ b/version/info.codefly.yaml @@ -1 +1 @@ -version: 0.19.11 +version: 0.19.12 From 8f998a38563ee4375ab9922c6e2bb4924424c0f8 Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:49:10 -0400 Subject: [PATCH 5/5] fix(deps): retain the Kubernetes module compatibility pins --- go.mod | 11 ++++++++--- go.sum | 20 ++++++++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index aee98297d..0231cda58 100644 --- a/go.mod +++ b/go.mod @@ -78,8 +78,8 @@ require ( google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 mvdan.cc/sh/v3 v3.14.1 - sigs.k8s.io/kustomize/api v0.21.3 - sigs.k8s.io/kustomize/kyaml v0.21.3 + sigs.k8s.io/kustomize/api v0.21.1 + sigs.k8s.io/kustomize/kyaml v0.21.1 ) require ( @@ -108,6 +108,7 @@ require ( github.com/cloudflare/circl v1.6.3 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/typeurl/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect github.com/dlclark/regexp2 v1.12.0 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -126,10 +127,14 @@ require ( github.com/go-openapi/jsonpointer v1.0.2 // indirect github.com/go-openapi/jsonreference v1.0.3 // indirect github.com/go-openapi/strfmt v0.27.4 // indirect + github.com/go-openapi/swag v0.29.2 // indirect + github.com/go-openapi/swag/cmdutils v0.29.2 // indirect github.com/go-openapi/swag/conv v0.29.2 // indirect + github.com/go-openapi/swag/fileutils v0.29.2 // indirect github.com/go-openapi/swag/jsonutils v0.29.2 // indirect github.com/go-openapi/swag/loading v0.29.2 // indirect github.com/go-openapi/swag/mangling v0.29.2 // indirect + github.com/go-openapi/swag/netutils v0.29.2 // indirect github.com/go-openapi/swag/pools v0.29.2 // indirect github.com/go-openapi/swag/stringutils v0.29.2 // indirect github.com/go-openapi/swag/typeutils v0.29.2 // indirect @@ -193,7 +198,7 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gotest.tools/v3 v3.5.1 // indirect - k8s.io/kube-openapi v0.0.0-20261001230523-97fa35140926 // indirect + k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 2dd7d9bce..1225fc57e 100644 --- a/go.sum +++ b/go.sum @@ -102,6 +102,7 @@ github.com/creativeprojects/go-selfupdate v1.7.0/go.mod h1:RVszRkQ0kVQB2hbqrsgrE github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE= github.com/cyphar/filepath-securejoin v0.7.0/go.mod h1:ymLGms/u3BYaviIiuKFnUx8EkQEZeK6cInNoAPJA3o4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= @@ -166,8 +167,13 @@ github.com/go-openapi/spec v1.0.1/go.mod h1:M//GWQGtDUAjnP37gE6fInLgaczB+FatoipV github.com/go-openapi/strfmt v0.27.4 h1:E1xsvHb8R9vsuYru4TP2PVVTv9AIgSFAUACUZrFQfCI= github.com/go-openapi/strfmt v0.27.4/go.mod h1:SCOff81PPhU26acBiW96jHaFycEbT2nkQRvojM+V2z0= github.com/go-openapi/swag v0.29.2 h1:9n8frkcsuQRA0INU31VbxNxt1zBaHxrHYGEf5BUjFi4= +github.com/go-openapi/swag v0.29.2/go.mod h1:RkCiX1gCVXWgLOavDXE3ALJrQ/VO2ziiIzOUs3z10xo= +github.com/go-openapi/swag/cmdutils v0.29.2 h1:cXEzX/nWCODon251f+1HgL75/R4XXML5IZxgLCWMoro= +github.com/go-openapi/swag/cmdutils v0.29.2/go.mod h1:XziaSVzYqkDvpWXzASfWajw2YvJrLscq610O2XiyqJw= github.com/go-openapi/swag/conv v0.29.2 h1:8c9shoB8l0QRSR6ymq1llHdBWDqpIgJfjTGHx3Vuhm0= github.com/go-openapi/swag/conv v0.29.2/go.mod h1:AZS0YigTNf8qNtD6WqJz/N4RUNmpr76SyjFGkq4+p6Q= +github.com/go-openapi/swag/fileutils v0.29.2 h1:mdUL+Vw5ah1fO1AvFCoHeIyv7YZsCz2MN3KNqd/lLVI= +github.com/go-openapi/swag/fileutils v0.29.2/go.mod h1:7QKmmodjAebaq61lGVJa8ldLdGeMF4DSgYv8tXOGGo0= github.com/go-openapi/swag/jsonutils v0.29.2 h1:uZNSD2/rJDYAfvsLYkykTzXuyxM3QpDKV9jhRHrMu6k= github.com/go-openapi/swag/jsonutils v0.29.2/go.mod h1:ONTdNvj3Y+IXRzfa17E+Rgt2ns/qiSOYjIo2K7v2yDs= github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.2 h1:w+Fd6EBOMGlC74GYGHqCLGyb3Bpams8TtNrgM/SG4jo= @@ -176,6 +182,8 @@ github.com/go-openapi/swag/loading v0.29.2 h1:QU1ry24e6r6Shoxe380Nx0X5iKkpRO+hJf github.com/go-openapi/swag/loading v0.29.2/go.mod h1:DqilDjuiJKETecsS4TRopWG1acUDsfqN39ZmQhPu6uI= github.com/go-openapi/swag/mangling v0.29.2 h1:ltdo3K0tTP4P6zWhVVWDU9D37/YYBuMJRdZxiS2Dtog= github.com/go-openapi/swag/mangling v0.29.2/go.mod h1:RjDi4TnItAenS3cgCXSpIl1kKbjG7il6pnmQVl/8bp8= +github.com/go-openapi/swag/netutils v0.29.2 h1:dBli6jyUa93sDS/XYWShJzf4+nnVfFKfvY9SLUaUzB8= +github.com/go-openapi/swag/netutils v0.29.2/go.mod h1:1pM6Xg/Um8E1eps3umWl8Hme3ByWXOtHsvFC0CULHUE= github.com/go-openapi/swag/pools v0.29.2 h1:PlGoDRF8WtSyXkedZZkpW3f9wDhFf3u8KtaBcmgmh8c= github.com/go-openapi/swag/pools v0.29.2/go.mod h1:V3lhxVT4qDYRqc2MRSSbLsTYIYV/2HlHafhyEkmzLIc= github.com/go-openapi/swag/stringutils v0.29.2 h1:lcnBxwAaysT3bMUVBfn9V/PkfVqkurpqufKU6rTUMGk= @@ -496,16 +504,16 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -k8s.io/kube-openapi v0.0.0-20261001230523-97fa35140926 h1:Fe0G8t8FaMDn2lpat0IHu3X9lHrVyGzDxhhCwKXKp28= -k8s.io/kube-openapi v0.0.0-20261001230523-97fa35140926/go.mod h1:3e2VxsK2svhjvkNik92kYU1a5kgau5tZeAuEWwoN4q0= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= mvdan.cc/sh/v3 v3.14.1 h1:bXkhQWNHCs0KZEChF8hYS6FC+T2N9mUZLbQv9blditI= mvdan.cc/sh/v3 v3.14.1/go.mod h1:syYCoFET8w9tvevxiXUtY8/ICrU+l26jHmhJDra3Vwo= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/kustomize/api v0.21.3 h1:DXGrbifGrAd+7AQ75Kx/tVhr+RLMCZFfAYl61HOR3T0= -sigs.k8s.io/kustomize/api v0.21.3/go.mod h1:JKlHjHXGh54cdoWVdKNqDscFzPb9z+sBk9upy9PBh6k= -sigs.k8s.io/kustomize/kyaml v0.21.3 h1:4ygzLsc8B1ep9dFuEivrJ2lFIAglL19dhPrWp1ioceY= -sigs.k8s.io/kustomize/kyaml v0.21.3/go.mod h1:HrAVNlYyEphOlRCcaSIQziGR+0ehwx6Xp1ucUrpC6hs= +sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= +sigs.k8s.io/kustomize/api v0.21.1/go.mod h1:f3wkKByTrgpgltLgySCntrYoq5d3q7aaxveSagwTlwI= +sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI= +sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=