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",