From 7a6291b44e37f0b6f8d498f481a733ea676674b3 Mon Sep 17 00:00:00 2001 From: Josef Hoerandtner Date: Thu, 9 Apr 2026 12:04:19 +0200 Subject: [PATCH 1/7] Add GCR helper implementation and tests for credential management --- .../gcrhelper/fakes/fake_gcrhelper.go | 185 ++++++++++++++++++ .../gcrhelper/gcrhelper.go | 123 ++++++++++++ .../gcrhelper/gcrhelper_suite_test.go | 13 ++ .../gcrhelper/gcrhelper_test.go | 108 ++++++++++ .../gcrhelper/package.go | 1 + 5 files changed, 430 insertions(+) create mode 100644 src/code.cloudfoundry.org/gcrhelper/fakes/fake_gcrhelper.go create mode 100644 src/code.cloudfoundry.org/gcrhelper/gcrhelper.go create mode 100644 src/code.cloudfoundry.org/gcrhelper/gcrhelper_suite_test.go create mode 100644 src/code.cloudfoundry.org/gcrhelper/gcrhelper_test.go create mode 100644 src/code.cloudfoundry.org/gcrhelper/package.go diff --git a/src/code.cloudfoundry.org/gcrhelper/fakes/fake_gcrhelper.go b/src/code.cloudfoundry.org/gcrhelper/fakes/fake_gcrhelper.go new file mode 100644 index 000000000..877d15da9 --- /dev/null +++ b/src/code.cloudfoundry.org/gcrhelper/fakes/fake_gcrhelper.go @@ -0,0 +1,185 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package fakes + +import ( + "sync" + + "code.cloudfoundry.org/gcrhelper" +) + +type FakeGCRHelper struct { + GetGCRCredentialsStub func() (string, string, error) + getGCRCredentialsMutex sync.RWMutex + getGCRCredentialsArgsForCall []struct{} + getGCRCredentialsReturns struct { + result1 string + result2 string + result3 error + } + getGCRCredentialsReturnsOnCall map[int]struct { + result1 string + result2 string + result3 error + } + IsGCRRepoStub func(string) (bool, error) + isGCRRepoMutex sync.RWMutex + isGCRRepoArgsForCall []struct { + arg1 string + } + isGCRRepoReturns struct { + result1 bool + result2 error + } + isGCRRepoReturnsOnCall map[int]struct { + result1 bool + result2 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeGCRHelper) GetGCRCredentials() (string, string, error) { + fake.getGCRCredentialsMutex.Lock() + ret, specificReturn := fake.getGCRCredentialsReturnsOnCall[len(fake.getGCRCredentialsArgsForCall)] + fake.getGCRCredentialsArgsForCall = append(fake.getGCRCredentialsArgsForCall, struct{}{}) + stub := fake.GetGCRCredentialsStub + fakeReturns := fake.getGCRCredentialsReturns + fake.recordInvocation("GetGCRCredentials", []interface{}{}) + fake.getGCRCredentialsMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1, ret.result2, ret.result3 + } + return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 +} + +func (fake *FakeGCRHelper) GetGCRCredentialsCallCount() int { + fake.getGCRCredentialsMutex.RLock() + defer fake.getGCRCredentialsMutex.RUnlock() + return len(fake.getGCRCredentialsArgsForCall) +} + +func (fake *FakeGCRHelper) GetGCRCredentialsCalls(stub func() (string, string, error)) { + fake.getGCRCredentialsMutex.Lock() + defer fake.getGCRCredentialsMutex.Unlock() + fake.GetGCRCredentialsStub = stub +} + +func (fake *FakeGCRHelper) GetGCRCredentialsReturns(result1 string, result2 string, result3 error) { + fake.getGCRCredentialsMutex.Lock() + defer fake.getGCRCredentialsMutex.Unlock() + fake.GetGCRCredentialsStub = nil + fake.getGCRCredentialsReturns = struct { + result1 string + result2 string + result3 error + }{result1, result2, result3} +} + +func (fake *FakeGCRHelper) GetGCRCredentialsReturnsOnCall(i int, result1 string, result2 string, result3 error) { + fake.getGCRCredentialsMutex.Lock() + defer fake.getGCRCredentialsMutex.Unlock() + fake.GetGCRCredentialsStub = nil + if fake.getGCRCredentialsReturnsOnCall == nil { + fake.getGCRCredentialsReturnsOnCall = make(map[int]struct { + result1 string + result2 string + result3 error + }) + } + fake.getGCRCredentialsReturnsOnCall[i] = struct { + result1 string + result2 string + result3 error + }{result1, result2, result3} +} + +func (fake *FakeGCRHelper) IsGCRRepo(arg1 string) (bool, error) { + fake.isGCRRepoMutex.Lock() + ret, specificReturn := fake.isGCRRepoReturnsOnCall[len(fake.isGCRRepoArgsForCall)] + fake.isGCRRepoArgsForCall = append(fake.isGCRRepoArgsForCall, struct { + arg1 string + }{arg1}) + stub := fake.IsGCRRepoStub + fakeReturns := fake.isGCRRepoReturns + fake.recordInvocation("IsGCRRepo", []interface{}{arg1}) + fake.isGCRRepoMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeGCRHelper) IsGCRRepoCallCount() int { + fake.isGCRRepoMutex.RLock() + defer fake.isGCRRepoMutex.RUnlock() + return len(fake.isGCRRepoArgsForCall) +} + +func (fake *FakeGCRHelper) IsGCRRepoCalls(stub func(string) (bool, error)) { + fake.isGCRRepoMutex.Lock() + defer fake.isGCRRepoMutex.Unlock() + fake.IsGCRRepoStub = stub +} + +func (fake *FakeGCRHelper) IsGCRRepoArgsForCall(i int) string { + fake.isGCRRepoMutex.RLock() + defer fake.isGCRRepoMutex.RUnlock() + argsForCall := fake.isGCRRepoArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeGCRHelper) IsGCRRepoReturns(result1 bool, result2 error) { + fake.isGCRRepoMutex.Lock() + defer fake.isGCRRepoMutex.Unlock() + fake.IsGCRRepoStub = nil + fake.isGCRRepoReturns = struct { + result1 bool + result2 error + }{result1, result2} +} + +func (fake *FakeGCRHelper) IsGCRRepoReturnsOnCall(i int, result1 bool, result2 error) { + fake.isGCRRepoMutex.Lock() + defer fake.isGCRRepoMutex.Unlock() + fake.IsGCRRepoStub = nil + if fake.isGCRRepoReturnsOnCall == nil { + fake.isGCRRepoReturnsOnCall = make(map[int]struct { + result1 bool + result2 error + }) + } + fake.isGCRRepoReturnsOnCall[i] = struct { + result1 bool + result2 error + }{result1, result2} +} + +func (fake *FakeGCRHelper) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeGCRHelper) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ gcrhelper.GCRHelper = new(FakeGCRHelper) diff --git a/src/code.cloudfoundry.org/gcrhelper/gcrhelper.go b/src/code.cloudfoundry.org/gcrhelper/gcrhelper.go new file mode 100644 index 000000000..415802cf4 --- /dev/null +++ b/src/code.cloudfoundry.org/gcrhelper/gcrhelper.go @@ -0,0 +1,123 @@ +// Package gcrhelper provides credential support for Google Container Registry +// (gcr.io) and Artifact Registry (*.pkg.dev). GCR is the legacy service; Google +// now routes gcr.io requests through Artifact Registry. Both URL patterns are +// supported. Authentication uses the GCE instance metadata server to obtain a +// short-lived OAuth2 token from the VM's attached service account. +package gcrhelper + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "regexp" + "sync" + "time" +) + +const ( + GCE_METADATA_TOKEN_URL = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" + GCR_USERNAME = "oauth2accesstoken" + GCR_REPO_REGEX = `([a-zA-Z0-9-]+\.)?gcr\.io|[a-zA-Z0-9-]+\.pkg\.dev` + + // metadataTimeout is intentionally short: the GCE metadata server is a + // link-local address (169.254.169.254) that responds in <1ms on GCE and + // typically fails immediately off-GCE due to no route. Since this is only + // attempted once per process lifetime (notOnGCE is set on first failure), + // 1s is a safe upper bound. + metadataTimeout = 1 * time.Second +) + +var gcrRepoRegex = regexp.MustCompile(GCR_REPO_REGEX) + +//go:generate counterfeiter -o fakes/fake_gcrhelper.go . GCRHelper +type GCRHelper interface { + IsGCRRepo(registryURL string) (bool, error) + GetGCRCredentials() (string, string, error) +} + +// TokenFetcher retrieves an OAuth2 access token for use with GCR/Artifact Registry. +// The default implementation calls the GCE instance metadata server, which +// automatically uses the VM's attached service account without any stored credentials. +type TokenFetcher func() (string, error) + +func DefaultTokenFetcher() (string, error) { + req, err := http.NewRequest("GET", GCE_METADATA_TOKEN_URL, nil) + if err != nil { + return "", err + } + req.Header.Set("Metadata-Flavor", "Google") + + client := &http.Client{Timeout: metadataTimeout} + resp, err := client.Do(req) + if err != nil { + return "", fmt.Errorf("failed to reach GCE metadata server: %s", err.Error()) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("GCE metadata server returned status %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + var tokenResponse struct { + AccessToken string `json:"access_token"` + } + if err := json.Unmarshal(body, &tokenResponse); err != nil { + return "", fmt.Errorf("failed to parse GCE metadata token response: %s", err.Error()) + } + if tokenResponse.AccessToken == "" { + return "", fmt.Errorf("empty access_token in GCE metadata response") + } + + return tokenResponse.AccessToken, nil +} + +type gcrHelper struct { + tokenFetcher TokenFetcher + mu sync.Mutex + notOnGCE bool +} + +func NewGCRHelper() GCRHelper { + return &gcrHelper{ + tokenFetcher: DefaultTokenFetcher, + } +} + +func NewGCRHelperWithTokenFetcher(fetcher TokenFetcher) GCRHelper { + if fetcher == nil { + fetcher = DefaultTokenFetcher + } + return &gcrHelper{ + tokenFetcher: fetcher, + } +} + +func (h *gcrHelper) IsGCRRepo(registryURL string) (bool, error) { + return gcrRepoRegex.MatchString(registryURL), nil +} + +func (h *gcrHelper) GetGCRCredentials() (string, string, error) { + h.mu.Lock() + defer h.mu.Unlock() + + if h.notOnGCE { + return "", "", nil + } + + token, err := h.tokenFetcher() + if err != nil { + // Not running on GCE or metadata unavailable — fall back to unauthenticated + // so that truly public GCR/Artifact Registry images still pull successfully. + // We remember this for the lifetime of the process to avoid a dial attempt + // on every subsequent container start. + h.notOnGCE = true + return "", "", nil + } + return GCR_USERNAME, token, nil +} diff --git a/src/code.cloudfoundry.org/gcrhelper/gcrhelper_suite_test.go b/src/code.cloudfoundry.org/gcrhelper/gcrhelper_suite_test.go new file mode 100644 index 000000000..6315fc35f --- /dev/null +++ b/src/code.cloudfoundry.org/gcrhelper/gcrhelper_suite_test.go @@ -0,0 +1,13 @@ +package gcrhelper_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestGcrhelper(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Gcrhelper Suite") +} diff --git a/src/code.cloudfoundry.org/gcrhelper/gcrhelper_test.go b/src/code.cloudfoundry.org/gcrhelper/gcrhelper_test.go new file mode 100644 index 000000000..57540166a --- /dev/null +++ b/src/code.cloudfoundry.org/gcrhelper/gcrhelper_test.go @@ -0,0 +1,108 @@ +package gcrhelper_test + +import ( + "fmt" + "sync/atomic" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "code.cloudfoundry.org/gcrhelper" +) + +var _ = Describe("Gcrhelper", func() { + var helper gcrhelper.GCRHelper + + BeforeEach(func() { + helper = gcrhelper.NewGCRHelper() + }) + + Describe("IsGCRRepo", func() { + DescribeTable("returns true for GCR/Artifact Registry URLs", + func(url string) { + isGCR, err := helper.IsGCRRepo(url) + Expect(err).NotTo(HaveOccurred()) + Expect(isGCR).To(BeTrue()) + }, + Entry("gcr.io", "gcr.io/my-project/my-image:tag"), + Entry("gcr.io with docker:// scheme", "docker://gcr.io/my-project/my-image:tag"), + Entry("us.gcr.io", "us.gcr.io/my-project/my-image:tag"), + Entry("eu.gcr.io", "eu.gcr.io/my-project/my-image:tag"), + Entry("asia.gcr.io", "asia.gcr.io/my-project/my-image:tag"), + Entry("Artifact Registry pkg.dev", "europe-west3-docker.pkg.dev/my-project/my-repo/my-image:tag"), + Entry("Artifact Registry with docker:// scheme", "docker://europe-west3-docker.pkg.dev/my-project/my-repo/my-image:tag"), + Entry("us-central1 Artifact Registry", "us-central1-docker.pkg.dev/my-project/my-repo/my-image:latest"), + ) + + DescribeTable("returns false for non-GCR URLs", + func(url string) { + isGCR, err := helper.IsGCRRepo(url) + Expect(err).NotTo(HaveOccurred()) + Expect(isGCR).To(BeFalse()) + }, + Entry("Docker Hub", "docker.io/cloudfoundry/diego-docker-app"), + Entry("Docker Hub with docker:// scheme", "docker://cloudfoundry/diego-docker-app"), + Entry("ECR", "555555555.dkr.ecr.us-east-1.amazonaws.com/my-image"), + Entry("private registry", "internal-registry.example.com:5000/my-repo/my-image:v2"), + Entry("preloaded rootfs", "preloaded:cflinuxfs4"), + ) + }) + + Describe("GetGCRCredentials", func() { + Context("when the token fetcher succeeds", func() { + BeforeEach(func() { + helper = gcrhelper.NewGCRHelperWithTokenFetcher(func() (string, error) { + return "ya29.fake-token", nil + }) + }) + + It("returns oauth2accesstoken and the metadata token", func() { + username, password, err := helper.GetGCRCredentials() + Expect(err).NotTo(HaveOccurred()) + Expect(username).To(Equal("oauth2accesstoken")) + Expect(password).To(Equal("ya29.fake-token")) + }) + + It("fetches a fresh token on every call (tokens are short-lived and metadata calls are <1ms on GCE)", func() { + var calls atomic.Int32 + helper = gcrhelper.NewGCRHelperWithTokenFetcher(func() (string, error) { + calls.Add(1) + return "ya29.fake-token", nil + }) + + _, _, _ = helper.GetGCRCredentials() + _, _, _ = helper.GetGCRCredentials() + _, _, _ = helper.GetGCRCredentials() + Expect(calls.Load()).To(Equal(int32(3))) + }) + }) + + Context("when the token fetcher fails (e.g. not running on GCE)", func() { + BeforeEach(func() { + helper = gcrhelper.NewGCRHelperWithTokenFetcher(func() (string, error) { + return "", fmt.Errorf("metadata server unreachable") + }) + }) + + It("returns empty credentials so public images still pull unauthenticated", func() { + username, password, err := helper.GetGCRCredentials() + Expect(err).NotTo(HaveOccurred()) + Expect(username).To(Equal("")) + Expect(password).To(Equal("")) + }) + + It("does not retry the metadata server after the first failure (one dial attempt per process lifetime)", func() { + var calls atomic.Int32 + helper = gcrhelper.NewGCRHelperWithTokenFetcher(func() (string, error) { + calls.Add(1) + return "", fmt.Errorf("metadata server unreachable") + }) + + _, _, _ = helper.GetGCRCredentials() + _, _, _ = helper.GetGCRCredentials() + _, _, _ = helper.GetGCRCredentials() + Expect(calls.Load()).To(Equal(int32(1))) + }) + }) + }) +}) diff --git a/src/code.cloudfoundry.org/gcrhelper/package.go b/src/code.cloudfoundry.org/gcrhelper/package.go new file mode 100644 index 000000000..d3002eaf2 --- /dev/null +++ b/src/code.cloudfoundry.org/gcrhelper/package.go @@ -0,0 +1 @@ +package gcrhelper // import "code.cloudfoundry.org/gcrhelper" From 23162f719e9be6b33bd7fec5bf3bb81be975c0c9 Mon Sep 17 00:00:00 2001 From: Josef Hoerandtner Date: Thu, 9 Apr 2026 16:19:23 +0200 Subject: [PATCH 2/7] Add gcrhelper to the list of files in rep spec --- packages/rep/spec | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/rep/spec b/packages/rep/spec index 0a4d8337e..429f96092 100644 --- a/packages/rep/spec +++ b/packages/rep/spec @@ -29,6 +29,7 @@ files: - code.cloudfoundry.org/vendor/code.cloudfoundry.org/dockerdriver/utils/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/durationjson/*.go # gosub - code.cloudfoundry.org/ecrhelper/*.go # gosub + - code.cloudfoundry.org/gcrhelper/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/eventhub/*.go # gosub - code.cloudfoundry.org/executor/*.go # gosub - code.cloudfoundry.org/executor/containermetrics/*.go # gosub From acd44221379192244e2d8d44948e0fe369c2ebb3 Mon Sep 17 00:00:00 2001 From: Josef Hoerandtner Date: Thu, 9 Apr 2026 16:26:24 +0200 Subject: [PATCH 3/7] Add gcrhelper to the list of files in auctioneer, bbs, cfdot, and rep_windows specs --- packages/auctioneer/spec | 1 + packages/bbs/spec | 1 + packages/cfdot/spec | 1 + packages/rep_windows/spec | 1 + 4 files changed, 4 insertions(+) diff --git a/packages/auctioneer/spec b/packages/auctioneer/spec index 335f84a7c..b6997f662 100644 --- a/packages/auctioneer/spec +++ b/packages/auctioneer/spec @@ -30,6 +30,7 @@ files: - code.cloudfoundry.org/vendor/code.cloudfoundry.org/diego-logging-client/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/durationjson/*.go # gosub - code.cloudfoundry.org/ecrhelper/*.go # gosub + - code.cloudfoundry.org/gcrhelper/*.go # gosub - code.cloudfoundry.org/executor/*.go # gosub - code.cloudfoundry.org/executor/containermetrics/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-diodes/*.go # gosub diff --git a/packages/bbs/spec b/packages/bbs/spec index 2249b5129..5a1aac69e 100644 --- a/packages/bbs/spec +++ b/packages/bbs/spec @@ -39,6 +39,7 @@ files: - code.cloudfoundry.org/vendor/code.cloudfoundry.org/diego-logging-client/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/durationjson/*.go # gosub - code.cloudfoundry.org/ecrhelper/*.go # gosub + - code.cloudfoundry.org/gcrhelper/*.go # gosub - code.cloudfoundry.org/executor/*.go # gosub - code.cloudfoundry.org/executor/containermetrics/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-diodes/*.go # gosub diff --git a/packages/cfdot/spec b/packages/cfdot/spec index 37f5b8598..f66a1ca41 100644 --- a/packages/cfdot/spec +++ b/packages/cfdot/spec @@ -22,6 +22,7 @@ files: - code.cloudfoundry.org/vendor/code.cloudfoundry.org/clock/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/diego-logging-client/*.go # gosub - code.cloudfoundry.org/ecrhelper/*.go # gosub + - code.cloudfoundry.org/gcrhelper/*.go # gosub - code.cloudfoundry.org/executor/*.go # gosub - code.cloudfoundry.org/executor/containermetrics/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-diodes/*.go # gosub diff --git a/packages/rep_windows/spec b/packages/rep_windows/spec index 385502bce..6ec25d2d2 100644 --- a/packages/rep_windows/spec +++ b/packages/rep_windows/spec @@ -30,6 +30,7 @@ files: - code.cloudfoundry.org/vendor/code.cloudfoundry.org/dockerdriver/utils/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/durationjson/*.go # gosub - code.cloudfoundry.org/ecrhelper/*.go # gosub + - code.cloudfoundry.org/gcrhelper/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/eventhub/*.go # gosub - code.cloudfoundry.org/executor/*.go # gosub - code.cloudfoundry.org/executor/containermetrics/*.go # gosub From e4749f5f6e51c09209a289751f366233bb1e8212 Mon Sep 17 00:00:00 2001 From: Josef Hoerandtner Date: Thu, 9 Apr 2026 16:52:58 +0200 Subject: [PATCH 4/7] Add GCR helper integration to builder for credential management --- .../dockerapplifecycle/builder/builder_runner.go | 15 +++++++++++++++ .../dockerapplifecycle/builder/main.go | 2 ++ 2 files changed, 17 insertions(+) diff --git a/src/code.cloudfoundry.org/dockerapplifecycle/builder/builder_runner.go b/src/code.cloudfoundry.org/dockerapplifecycle/builder/builder_runner.go index b43070278..6bd2b7fb5 100644 --- a/src/code.cloudfoundry.org/dockerapplifecycle/builder/builder_runner.go +++ b/src/code.cloudfoundry.org/dockerapplifecycle/builder/builder_runner.go @@ -11,6 +11,7 @@ import ( "code.cloudfoundry.org/dockerapplifecycle/helpers" "code.cloudfoundry.org/dockerapplifecycle/protocol" "code.cloudfoundry.org/ecrhelper" + "code.cloudfoundry.org/gcrhelper" "github.com/containers/image/v5/types" ) @@ -35,6 +36,7 @@ type Builder struct { DockerPassword string DockerEmail string ECRHelper ecrhelper.ECRHelper + GCRHelper gcrhelper.GCRHelper } func (builder *Builder) Run(signals <-chan os.Signal, ready chan<- struct{}) error { @@ -128,6 +130,19 @@ func (builder Builder) build() <-chan error { } func (builder Builder) getCredentials() (string, string, error) { + if builder.DockerUser == "" && builder.DockerPassword == "" { + isGCRRepo, err := builder.GCRHelper.IsGCRRepo(builder.RegistryURL) + if err != nil { + return "", "", fmt.Errorf( + "failed to check whether the registry URL is a GCR/Artifact Registry repo: %s", + err.Error(), + ) + } + if isGCRRepo { + return builder.GCRHelper.GetGCRCredentials() + } + } + isECRRepo, err := builder.ECRHelper.IsECRRepo(builder.RegistryURL) if err != nil { return "", "", fmt.Errorf( diff --git a/src/code.cloudfoundry.org/dockerapplifecycle/builder/main.go b/src/code.cloudfoundry.org/dockerapplifecycle/builder/main.go index 1989d7ca2..9b76c7b8f 100644 --- a/src/code.cloudfoundry.org/dockerapplifecycle/builder/main.go +++ b/src/code.cloudfoundry.org/dockerapplifecycle/builder/main.go @@ -10,6 +10,7 @@ import ( "code.cloudfoundry.org/dockerapplifecycle/helpers" "code.cloudfoundry.org/ecrhelper" + "code.cloudfoundry.org/gcrhelper" "github.com/tedsuo/ifrit" "github.com/tedsuo/ifrit/grouper" "github.com/tedsuo/ifrit/sigmon" @@ -141,6 +142,7 @@ func main() { DockerPassword: *dockerPassword, DockerEmail: *dockerEmail, ECRHelper: ecrhelper.NewECRHelper(), + GCRHelper: gcrhelper.NewGCRHelper(), } members := grouper.Members{ From f3f0c64cd256c81445cd583e51d1ea1e56cac351 Mon Sep 17 00:00:00 2001 From: Josef Hoerandtner Date: Thu, 9 Apr 2026 16:55:39 +0200 Subject: [PATCH 5/7] Add gcrhelper to the list of files in docker_app_lifecycle spec --- packages/docker_app_lifecycle/spec | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/docker_app_lifecycle/spec b/packages/docker_app_lifecycle/spec index 7853073d7..236b3d95e 100644 --- a/packages/docker_app_lifecycle/spec +++ b/packages/docker_app_lifecycle/spec @@ -27,6 +27,7 @@ files: - code.cloudfoundry.org/dockerapplifecycle/launcher/*.go # gosub - code.cloudfoundry.org/dockerapplifecycle/protocol/*.go # gosub - code.cloudfoundry.org/ecrhelper/*.go # gosub + - code.cloudfoundry.org/gcrhelper/*.go # gosub - code.cloudfoundry.org/vendor/github.com/BurntSushi/toml/*.go # gosub - code.cloudfoundry.org/vendor/github.com/BurntSushi/toml/internal/*.go # gosub - code.cloudfoundry.org/vendor/github.com/aws/aws-sdk-go-v2/aws/*.go # gosub From 07aa853c1af7e32e2951a28b8e238cd3e527fd59 Mon Sep 17 00:00:00 2001 From: Josef Hoerandtner Date: Thu, 9 Apr 2026 22:00:07 +0200 Subject: [PATCH 6/7] Add gcrhelper to the list of files in auctioneer, bbs, and rep specs --- packages/auctioneer/spec | 2 +- packages/bbs/spec | 2 +- packages/rep/spec | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/auctioneer/spec b/packages/auctioneer/spec index b6997f662..79d1fe50b 100644 --- a/packages/auctioneer/spec +++ b/packages/auctioneer/spec @@ -30,9 +30,9 @@ files: - code.cloudfoundry.org/vendor/code.cloudfoundry.org/diego-logging-client/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/durationjson/*.go # gosub - code.cloudfoundry.org/ecrhelper/*.go # gosub - - code.cloudfoundry.org/gcrhelper/*.go # gosub - code.cloudfoundry.org/executor/*.go # gosub - code.cloudfoundry.org/executor/containermetrics/*.go # gosub + - code.cloudfoundry.org/gcrhelper/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-diodes/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-loggregator/v9/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-loggregator/v9/rpc/loggregator_v2/*.go # gosub diff --git a/packages/bbs/spec b/packages/bbs/spec index 5a1aac69e..0a4bd50ac 100644 --- a/packages/bbs/spec +++ b/packages/bbs/spec @@ -39,9 +39,9 @@ files: - code.cloudfoundry.org/vendor/code.cloudfoundry.org/diego-logging-client/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/durationjson/*.go # gosub - code.cloudfoundry.org/ecrhelper/*.go # gosub - - code.cloudfoundry.org/gcrhelper/*.go # gosub - code.cloudfoundry.org/executor/*.go # gosub - code.cloudfoundry.org/executor/containermetrics/*.go # gosub + - code.cloudfoundry.org/gcrhelper/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-diodes/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-loggregator/v9/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-loggregator/v9/rpc/loggregator_v2/*.go # gosub diff --git a/packages/rep/spec b/packages/rep/spec index 429f96092..826d7b6d0 100644 --- a/packages/rep/spec +++ b/packages/rep/spec @@ -29,7 +29,6 @@ files: - code.cloudfoundry.org/vendor/code.cloudfoundry.org/dockerdriver/utils/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/durationjson/*.go # gosub - code.cloudfoundry.org/ecrhelper/*.go # gosub - - code.cloudfoundry.org/gcrhelper/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/eventhub/*.go # gosub - code.cloudfoundry.org/executor/*.go # gosub - code.cloudfoundry.org/executor/containermetrics/*.go # gosub @@ -54,6 +53,7 @@ files: - code.cloudfoundry.org/vendor/code.cloudfoundry.org/garden/server/streamer/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/garden/server/timebomb/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/garden/transport/*.go # gosub + - code.cloudfoundry.org/gcrhelper/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-diodes/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-loggregator/v9/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-loggregator/v9/rpc/loggregator_v2/*.go # gosub From 9eb85394da021a53461cf4e6f6faca295f1a6304 Mon Sep 17 00:00:00 2001 From: Josef Hoerandtner Date: Thu, 9 Apr 2026 23:52:34 +0200 Subject: [PATCH 7/7] Move gcrhelper to the correct location in cfdot and rep_windows specs --- packages/cfdot/spec | 2 +- packages/rep_windows/spec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cfdot/spec b/packages/cfdot/spec index f66a1ca41..08393afc2 100644 --- a/packages/cfdot/spec +++ b/packages/cfdot/spec @@ -22,9 +22,9 @@ files: - code.cloudfoundry.org/vendor/code.cloudfoundry.org/clock/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/diego-logging-client/*.go # gosub - code.cloudfoundry.org/ecrhelper/*.go # gosub - - code.cloudfoundry.org/gcrhelper/*.go # gosub - code.cloudfoundry.org/executor/*.go # gosub - code.cloudfoundry.org/executor/containermetrics/*.go # gosub + - code.cloudfoundry.org/gcrhelper/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-diodes/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-loggregator/v9/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-loggregator/v9/rpc/loggregator_v2/*.go # gosub diff --git a/packages/rep_windows/spec b/packages/rep_windows/spec index 6ec25d2d2..1bb1ac3f0 100644 --- a/packages/rep_windows/spec +++ b/packages/rep_windows/spec @@ -30,7 +30,6 @@ files: - code.cloudfoundry.org/vendor/code.cloudfoundry.org/dockerdriver/utils/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/durationjson/*.go # gosub - code.cloudfoundry.org/ecrhelper/*.go # gosub - - code.cloudfoundry.org/gcrhelper/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/eventhub/*.go # gosub - code.cloudfoundry.org/executor/*.go # gosub - code.cloudfoundry.org/executor/containermetrics/*.go # gosub @@ -55,6 +54,7 @@ files: - code.cloudfoundry.org/vendor/code.cloudfoundry.org/garden/server/streamer/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/garden/server/timebomb/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/garden/transport/*.go # gosub + - code.cloudfoundry.org/gcrhelper/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-diodes/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-loggregator/v9/*.go # gosub - code.cloudfoundry.org/vendor/code.cloudfoundry.org/go-loggregator/v9/rpc/loggregator_v2/*.go # gosub