From 34faae534937ed588eaf917837644005195cb187 Mon Sep 17 00:00:00 2001 From: Anna Pendleton <13793293+annapendleton@users.noreply.github.com> Date: Tue, 1 Sep 2026 21:53:35 -0700 Subject: [PATCH] ate-setup: widen digest-lookup auth and pass through pinned tags Pinning a pre-built image to its digest authenticated the lookup with the docker config file alone. ko resolves its own credentials through a multi-keychain, and the GCP half of it is what the flag's own audience needs: someone installing a published release onto GKE, whose Artifact Registry credentials are gcloud's rather than a helper wired into ~/.docker/config.json, got a working build from source and a 401 from --image-repo. Add GCP to the keychain. ECR and ACR need credential-helper modules this repository does not depend on, so those registries still need a docker login, and the doc comment no longer claims the lookup authenticates the way ko does. Pass a tag that already carries a digest through as written. The reference was built as /: and then had the looked-up digest appended unconditionally, so --image-tag v1@sha256:... produced /:v1@sha256:...@sha256:..., which no registry can parse. That input resolved before the tags were pinned. Document both in commands.md, which described the digest lookup as needing read access to the repository but never said how it authenticates. --- cmd/ate-setup/commands.md | 10 ++++++++++ cmd/ate-setup/internal/images/digest.go | 20 ++++++++++++++----- cmd/ate-setup/internal/images/prebuilt.go | 6 ++++++ .../internal/images/prebuilt_test.go | 9 +++++++++ 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/cmd/ate-setup/commands.md b/cmd/ate-setup/commands.md index b2c565e37a..0506efd3ac 100644 --- a/cmd/ate-setup/commands.md +++ b/cmd/ate-setup/commands.md @@ -60,6 +60,16 @@ Each reference is then pinned to the digest its tag names, which takes one HEAD request per image, so the installer needs read access to `REPO` and not only the cluster does. +That read is authenticated with the docker config file and the credential +helpers it names, plus gcloud's own credentials for GCR and Artifact Registry: +Application Default Credentials, falling back to the `gcloud` CLI. Installing a +release onto GKE therefore needs no `~/.docker/config.json` entry. Amazon ECR +and Azure Container Registry need credential-helper modules this repository does +not depend on, so those registries need a `docker login` first. + +`TAG` may itself carry a digest, as in `--image-tag v0.0.0@sha256:...`. A tag +that already names a manifest is used as written, and is not looked up. + ## Deploy | `ate-setup` | `hack/install-ate.sh` | diff --git a/cmd/ate-setup/internal/images/digest.go b/cmd/ate-setup/internal/images/digest.go index 7cca8979d3..e72b71310d 100644 --- a/cmd/ate-setup/internal/images/digest.go +++ b/cmd/ate-setup/internal/images/digest.go @@ -20,9 +20,21 @@ import ( "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/google" "github.com/google/go-containerregistry/pkg/v1/remote" ) +// keychain authenticates the digest lookups. +// +// The docker config file and the credential helpers it names, plus GCP. ko +// resolves credentials through a multi-keychain of its own, and matching the +// GCP half of it matters: a user whose Artifact Registry credentials are +// gcloud's rather than a helper wired into ~/.docker/config.json would +// otherwise get a working build from source and a 401 from --image-repo. ECR +// and ACR need credential-helper modules this repository does not depend on, +// so those registries need a docker login. +var keychain = authn.NewMultiKeychain(authn.DefaultKeychain, google.Keychain) + // Digester resolves a tagged image reference to the digest its tag currently // names, in "sha256:..." form. Prebuilt takes one so tests can rewrite // manifests without a registry. @@ -30,10 +42,8 @@ type Digester func(ctx context.Context, ref string) (string, error) // RemoteDigest asks ref's registry which manifest the tag names. // -// One HEAD request per image, needing only read access, and authenticated the -// way docker and ko are: from the docker config file and the credential helpers -// it names. A localhost registry is contacted over plain HTTP, which is what -// the Kind local registry serves. +// One HEAD request per image, needing only read access. A localhost registry is +// contacted over plain HTTP, which is what the Kind local registry serves. func RemoteDigest(ctx context.Context, ref string) (string, error) { parsed, err := name.ParseReference(ref) if err != nil { @@ -41,7 +51,7 @@ func RemoteDigest(ctx context.Context, ref string) (string, error) { } desc, err := remote.Head(parsed, remote.WithContext(ctx), - remote.WithAuthFromKeychain(authn.DefaultKeychain), + remote.WithAuthFromKeychain(keychain), ) if err != nil { return "", fmt.Errorf("resolving %s to a digest: %w", ref, err) diff --git a/cmd/ate-setup/internal/images/prebuilt.go b/cmd/ate-setup/internal/images/prebuilt.go index 2631a3bcaf..cf7847ea5a 100644 --- a/cmd/ate-setup/internal/images/prebuilt.go +++ b/cmd/ate-setup/internal/images/prebuilt.go @@ -98,6 +98,12 @@ func (p *Prebuilt) ResolveBytes(ctx context.Context, manifest []byte) ([]byte, e // and leaves the reference legible in `kubectl get`. func (p *Prebuilt) pin(ctx context.Context, image string) (string, error) { tagged := p.src.Repo + "/" + image + ":" + p.src.Tag + // A tag that already carries one is pinned as it stands. Looking it up + // would only append the same digest a second time, and the reference the + // caller wrote is the one they meant. + if strings.Contains(p.src.Tag, "@") { + return tagged, nil + } if ref, ok := p.pinned[tagged]; ok { return ref, nil } diff --git a/cmd/ate-setup/internal/images/prebuilt_test.go b/cmd/ate-setup/internal/images/prebuilt_test.go index 047ba391d3..27220adb44 100644 --- a/cmd/ate-setup/internal/images/prebuilt_test.go +++ b/cmd/ate-setup/internal/images/prebuilt_test.go @@ -93,6 +93,15 @@ func TestPrebuiltResolveBytes(t *testing.T) { in: "spec:\n workerImage: ko://github.com/agent-substrate/substrate/cmd/ateom-gvisor\n", want: "spec:\n workerImage: example.com/substrate/ateom-gvisor:v1.2.3" + digestSuffix + "\n", }, + { + // A tag can carry its own digest, and then there is nothing to look + // up. The stub would answer with testDigest a second time, so the + // expected output is also what catches a redundant lookup. + name: "tag already carries a digest", + src: images.Source{Repo: "example.com/substrate", Tag: "v1.2.3" + digestSuffix}, + in: "image: ko://github.com/agent-substrate/substrate/cmd/ateapi\n", + want: "image: example.com/substrate/ateapi:v1.2.3" + digestSuffix + "\n", + }, { name: "nested package maps to its image name", in: "image: ko://github.com/agent-substrate/substrate/demos/multi-template/fspersist\n",