From 8b82dc62222eaea6af0d234f5f12eec37340c6e2 Mon Sep 17 00:00:00 2001 From: Christian Weichel Date: Wed, 4 May 2022 19:24:07 +0000 Subject: [PATCH 1/2] [image-builder] Improve error handling --- components/image-builder-mk3/go.mod | 2 + components/image-builder-mk3/go.sum | 2 + components/image-builder-mk3/hack/tools.go | 11 + .../pkg/orchestrator/orchestrator.go | 32 ++- .../pkg/orchestrator/orchestrator_test.go | 61 ++++++ .../image-builder-mk3/pkg/resolve/resolve.go | 20 +- .../pkg/resolve/resolve_mock_test.go | 126 ++++++++++++ .../pkg/resolve/resolve_test.go | 193 ++++++++++++++++++ 8 files changed, 435 insertions(+), 12 deletions(-) create mode 100644 components/image-builder-mk3/hack/tools.go create mode 100644 components/image-builder-mk3/pkg/resolve/resolve_mock_test.go create mode 100644 components/image-builder-mk3/pkg/resolve/resolve_test.go diff --git a/components/image-builder-mk3/go.mod b/components/image-builder-mk3/go.mod index aa04307c6a1deb..a694d9d71f4c76 100644 --- a/components/image-builder-mk3/go.mod +++ b/components/image-builder-mk3/go.mod @@ -53,11 +53,13 @@ require ( github.com/uber/jaeger-client-go v2.29.1+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect go.uber.org/atomic v1.8.0 // indirect + golang.org/x/mod v0.4.2 // indirect golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + golang.org/x/tools v0.1.2 // indirect google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect ) diff --git a/components/image-builder-mk3/go.sum b/components/image-builder-mk3/go.sum index b15161f5666a94..840ede6821beb8 100644 --- a/components/image-builder-mk3/go.sum +++ b/components/image-builder-mk3/go.sum @@ -418,6 +418,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -605,6 +606,7 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/components/image-builder-mk3/hack/tools.go b/components/image-builder-mk3/hack/tools.go new file mode 100644 index 00000000000000..e0890244922d9d --- /dev/null +++ b/components/image-builder-mk3/hack/tools.go @@ -0,0 +1,11 @@ +// Copyright (c) 2021 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +//go:build tools_only + +package testing_tools + +import ( + _ "github.com/golang/mock/mockgen" +) diff --git a/components/image-builder-mk3/pkg/orchestrator/orchestrator.go b/components/image-builder-mk3/pkg/orchestrator/orchestrator.go index c8102ea19f8937..d2f3d51108ce5d 100644 --- a/components/image-builder-mk3/pkg/orchestrator/orchestrator.go +++ b/components/image-builder-mk3/pkg/orchestrator/orchestrator.go @@ -156,7 +156,7 @@ func (o *Orchestrator) ResolveBaseImage(ctx context.Context, req *protocol.Resol refstr, err := o.getAbsoluteImageRef(ctx, req.Ref, reqauth) if err != nil { - return nil, status.Errorf(codes.Internal, "cannot resolve base image ref: %v", err) + return nil, err } return &protocol.ResolveBaseImageResponse{ @@ -176,6 +176,9 @@ func (o *Orchestrator) ResolveWorkspaceImage(ctx context.Context, req *protocol. reqauth := o.AuthResolver.ResolveRequestAuth(req.Auth) baseref, err := o.getBaseImageRef(ctx, req.Source, reqauth) + if _, ok := status.FromError(err); err != nil && ok { + return nil, err + } if err != nil { return nil, status.Errorf(codes.Internal, "cannot resolve base image: %s", err.Error()) } @@ -224,11 +227,11 @@ func (o *Orchestrator) Build(req *protocol.BuildRequest, resp protocol.ImageBuil reqauth := o.AuthResolver.ResolveRequestAuth(req.Auth) baseref, err := o.getBaseImageRef(ctx, req.Source, reqauth) - if xerrors.Is(err, resolve.ErrNotFound) { - return status.Error(codes.NotFound, "cannot resolve base image") + if _, ok := status.FromError(err); err != nil && ok { + return err } if err != nil { - return status.Errorf(codes.Internal, "cannot resolve base image: %q", err) + return status.Errorf(codes.Internal, "cannot resolve base image: %s", err.Error()) } wsrefstr, err := o.getWorkspaceImageRef(ctx, baseref) if err != nil { @@ -249,7 +252,7 @@ func (o *Orchestrator) Build(req *protocol.BuildRequest, resp protocol.ImageBuil // If we didn't build it and the base image doesn't exist anymore, getWorkspaceImageRef will have failed to resolve the baseref. baserefAbsolute, err := o.getAbsoluteImageRef(ctx, baseref, auth.AllowedAuthForAll()) if err != nil { - return status.Errorf(codes.Internal, "cannot resolve base image ref: %q", err) + return err } // image has already been built - no need for us to start building @@ -532,9 +535,12 @@ func (o *Orchestrator) checkImageExists(ctx context.Context, ref string, authent span.SetTag("ref", ref) _, err = o.RefResolver.Resolve(ctx, ref, resolve.WithAuthentication(authentication)) - if err == resolve.ErrNotFound { + if errors.Is(err, resolve.ErrNotFound) { return false, nil } + if errors.Is(err, resolve.ErrUnauthorized) { + return false, status.Errorf(codes.Unauthenticated, "cannot check if image exists: %q", err) + } if err != nil { return false, err } @@ -546,10 +552,20 @@ func (o *Orchestrator) checkImageExists(ctx context.Context, ref string, authent func (o *Orchestrator) getAbsoluteImageRef(ctx context.Context, ref string, allowedAuth auth.AllowedAuthFor) (res string, err error) { auth, err := allowedAuth.GetAuthFor(o.Auth, ref) if err != nil { - return "", xerrors.Errorf("cannt resolve base image ref: %w", err) + return "", status.Errorf(codes.InvalidArgument, "cannt resolve base image ref: %v", err) } - return o.RefResolver.Resolve(ctx, ref, resolve.WithAuthentication(auth)) + ref, err = o.RefResolver.Resolve(ctx, ref, resolve.WithAuthentication(auth)) + if xerrors.Is(err, resolve.ErrNotFound) { + return "", status.Error(codes.NotFound, "cannot resolve image") + } + if xerrors.Is(err, resolve.ErrUnauthorized) { + return "", status.Error(codes.Unauthenticated, "cannot resolve image") + } + if err != nil { + return "", status.Errorf(codes.Internal, "cannot resolve image: %v", err) + } + return ref, nil } func (o *Orchestrator) getBaseImageRef(ctx context.Context, bs *protocol.BuildSource, allowedAuth auth.AllowedAuthFor) (res string, err error) { diff --git a/components/image-builder-mk3/pkg/orchestrator/orchestrator_test.go b/components/image-builder-mk3/pkg/orchestrator/orchestrator_test.go index afb307bddbbec9..fa65fa955c5808 100644 --- a/components/image-builder-mk3/pkg/orchestrator/orchestrator_test.go +++ b/components/image-builder-mk3/pkg/orchestrator/orchestrator_test.go @@ -18,6 +18,9 @@ import ( wsmanapi "github.com/gitpod-io/gitpod/ws-manager/api" wsmock "github.com/gitpod-io/gitpod/ws-manager/api/mock" "github.com/golang/mock/gomock" + "github.com/google/go-cmp/cmp" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) func TestBuild(t *testing.T) { @@ -144,3 +147,61 @@ func TestBuild(t *testing.T) { }) } } + +type unauthenticatedResolver struct{} + +func (unauthenticatedResolver) Resolve(ctx context.Context, ref string, opts ...resolve.DockerRefResolverOption) (res string, err error) { + return "", resolve.ErrUnauthorized +} + +func TestResolveBaseImage(t *testing.T) { + type Expectation struct { + Code codes.Code + } + ref := "some-image:latest" + tests := []struct { + Name string + Resolver resolve.DockerRefResolver + Expectation Expectation + }{ + { + Name: "not found", + Resolver: resolve.MockRefResolver{}, + Expectation: Expectation{ + Code: codes.NotFound, + }, + }, + { + Name: "not authenticated", + Resolver: unauthenticatedResolver{}, + Expectation: Expectation{ + Code: codes.Unauthenticated, + }, + }, + } + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + o, err := NewOrchestratingBuilder(config.Configuration{ + WorkspaceManager: config.WorkspaceManagerConfig{ + Client: wsmock.NewMockWorkspaceManagerClient(ctrl), + }, + }) + if err != nil { + t.Fatal(err) + } + o.RefResolver = test.Resolver + + _, err = o.ResolveBaseImage(context.Background(), &api.ResolveBaseImageRequest{ + Ref: ref, + }) + act := Expectation{Code: status.Code(err)} + if diff := cmp.Diff(test.Expectation, act); diff != "" { + t.Errorf("ResolveBaseImage() mismatch (-want +got):\n%s", diff) + } + }) + } + +} diff --git a/components/image-builder-mk3/pkg/resolve/resolve.go b/components/image-builder-mk3/pkg/resolve/resolve.go index ee74dfbeb26a5a..fb1c891b608c4a 100644 --- a/components/image-builder-mk3/pkg/resolve/resolve.go +++ b/components/image-builder-mk3/pkg/resolve/resolve.go @@ -26,8 +26,13 @@ import ( ociv1 "github.com/opencontainers/image-spec/specs-go/v1" ) -// ErrNotFound is returned when the reference was not found -var ErrNotFound = xerrors.Errorf("not found") +var ( + // ErrNotFound is returned when the reference was not found + ErrNotFound = xerrors.Errorf("not found") + + // ErrNotFound is returned when we're not authorized to return the reference + ErrUnauthorized = xerrors.Errorf("not authorized") +) // StandaloneRefResolver can resolve image references without a Docker daemon type StandaloneRefResolver struct { @@ -96,8 +101,12 @@ func (sr *StandaloneRefResolver) Resolve(ctx context.Context, ref string, opts . span.LogKV("normalized-ref", nref) res, desc, err := r.Resolve(ctx, nref) - if err != nil && strings.Contains(err.Error(), "not found") { - err = ErrNotFound + if err != nil { + if strings.Contains(err.Error(), "not found") { + err = ErrNotFound + } else if strings.Contains(err.Error(), "Unauthorized") { + err = ErrUnauthorized + } return } fetcher, err := r.Fetcher(ctx, res) @@ -137,6 +146,9 @@ func (sr *StandaloneRefResolver) Resolve(ctx context.Context, ref string, opts . var dgst digest.Digest for _, mf := range mfl.Manifests { + if mf.Platform == nil { + continue + } if fmt.Sprintf("%s-%s", mf.Platform.OS, mf.Platform.Architecture) == "linux-amd64" { dgst = mf.Digest break diff --git a/components/image-builder-mk3/pkg/resolve/resolve_mock_test.go b/components/image-builder-mk3/pkg/resolve/resolve_mock_test.go new file mode 100644 index 00000000000000..41b3c234574e13 --- /dev/null +++ b/components/image-builder-mk3/pkg/resolve/resolve_mock_test.go @@ -0,0 +1,126 @@ +// Copyright (c) 2022 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/containerd/containerd/remotes (interfaces: Resolver,Fetcher) + +// Package resolve_test is a generated GoMock package. +package resolve_test + +import ( + context "context" + io "io" + reflect "reflect" + + remotes "github.com/containerd/containerd/remotes" + gomock "github.com/golang/mock/gomock" + v1 "github.com/opencontainers/image-spec/specs-go/v1" +) + +// MockResolver is a mock of Resolver interface. +type MockResolver struct { + ctrl *gomock.Controller + recorder *MockResolverMockRecorder +} + +// MockResolverMockRecorder is the mock recorder for MockResolver. +type MockResolverMockRecorder struct { + mock *MockResolver +} + +// NewMockResolver creates a new mock instance. +func NewMockResolver(ctrl *gomock.Controller) *MockResolver { + mock := &MockResolver{ctrl: ctrl} + mock.recorder = &MockResolverMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockResolver) EXPECT() *MockResolverMockRecorder { + return m.recorder +} + +// Fetcher mocks base method. +func (m *MockResolver) Fetcher(arg0 context.Context, arg1 string) (remotes.Fetcher, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Fetcher", arg0, arg1) + ret0, _ := ret[0].(remotes.Fetcher) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Fetcher indicates an expected call of Fetcher. +func (mr *MockResolverMockRecorder) Fetcher(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fetcher", reflect.TypeOf((*MockResolver)(nil).Fetcher), arg0, arg1) +} + +// Pusher mocks base method. +func (m *MockResolver) Pusher(arg0 context.Context, arg1 string) (remotes.Pusher, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Pusher", arg0, arg1) + ret0, _ := ret[0].(remotes.Pusher) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Pusher indicates an expected call of Pusher. +func (mr *MockResolverMockRecorder) Pusher(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Pusher", reflect.TypeOf((*MockResolver)(nil).Pusher), arg0, arg1) +} + +// Resolve mocks base method. +func (m *MockResolver) Resolve(arg0 context.Context, arg1 string) (string, v1.Descriptor, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Resolve", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(v1.Descriptor) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Resolve indicates an expected call of Resolve. +func (mr *MockResolverMockRecorder) Resolve(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Resolve", reflect.TypeOf((*MockResolver)(nil).Resolve), arg0, arg1) +} + +// MockFetcher is a mock of Fetcher interface. +type MockFetcher struct { + ctrl *gomock.Controller + recorder *MockFetcherMockRecorder +} + +// MockFetcherMockRecorder is the mock recorder for MockFetcher. +type MockFetcherMockRecorder struct { + mock *MockFetcher +} + +// NewMockFetcher creates a new mock instance. +func NewMockFetcher(ctrl *gomock.Controller) *MockFetcher { + mock := &MockFetcher{ctrl: ctrl} + mock.recorder = &MockFetcherMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFetcher) EXPECT() *MockFetcherMockRecorder { + return m.recorder +} + +// Fetch mocks base method. +func (m *MockFetcher) Fetch(arg0 context.Context, arg1 v1.Descriptor) (io.ReadCloser, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Fetch", arg0, arg1) + ret0, _ := ret[0].(io.ReadCloser) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Fetch indicates an expected call of Fetch. +func (mr *MockFetcherMockRecorder) Fetch(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fetch", reflect.TypeOf((*MockFetcher)(nil).Fetch), arg0, arg1) +} diff --git a/components/image-builder-mk3/pkg/resolve/resolve_test.go b/components/image-builder-mk3/pkg/resolve/resolve_test.go new file mode 100644 index 00000000000000..de04c5e2dc1517 --- /dev/null +++ b/components/image-builder-mk3/pkg/resolve/resolve_test.go @@ -0,0 +1,193 @@ +// Copyright (c) 2022 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +//go:generate sh -c "mockgen -package resolve_test github.com/containerd/containerd/remotes Resolver,Fetcher > resolve_mock_test.go" +//go:generate leeway run components:update-license-header + +package resolve_test + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "testing" + + "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/remotes" + "github.com/gitpod-io/gitpod/image-builder/pkg/resolve" + "github.com/golang/mock/gomock" + "github.com/google/go-cmp/cmp" + "github.com/opencontainers/go-digest" + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" +) + +func TestStandaloneRefResolverResolve(t *testing.T) { + type Expectation struct { + Ref string + Error string + } + type ResolveResponse struct { + Error error + ResolvedRef string + NormalisedRef string + MF *ociv1.Manifest + Index *ociv1.Index + } + tests := []struct { + Name string + ResolveResponse ResolveResponse + Expectation Expectation + Ref string + }{ + { + Name: "basic resolve", + Ref: "docker.io/library/alpine:latest", + ResolveResponse: ResolveResponse{ + MF: &ociv1.Manifest{Config: ociv1.Descriptor{Size: 1}}, + }, + Expectation: Expectation{ + Ref: "docker.io/library/alpine@sha256:25d33b2d291ce47c3e4059589766ed98fadab639577efe5e9c89e4a4b50888fc", + }, + }, + { + Name: "with-port", + Ref: "some-internal-registry.customer.com:5000/gitpod/base-image:latest", + ResolveResponse: ResolveResponse{ + MF: &ociv1.Manifest{Config: ociv1.Descriptor{Size: 1}}, + }, + Expectation: Expectation{ + Ref: "some-internal-registry.customer.com:5000/gitpod/base-image@sha256:25d33b2d291ce47c3e4059589766ed98fadab639577efe5e9c89e4a4b50888fc", + }, + }, + { + Name: "with-broken-index", + Ref: "docker.io/library/alpine:latest", + ResolveResponse: ResolveResponse{ + Index: &ociv1.Index{ + Manifests: []ociv1.Descriptor{ + { + MediaType: ociv1.MediaTypeImageManifest, + Digest: digest.FromString(""), + }, + }, + }, + }, + Expectation: Expectation{Error: "no manifest for platform linux-amd64 found"}, + }, + { + Name: "with-working-index", + Ref: "docker.io/library/alpine:latest", + ResolveResponse: ResolveResponse{ + Index: &ociv1.Index{ + Manifests: []ociv1.Descriptor{ + { + MediaType: ociv1.MediaTypeImageManifest, + Digest: digest.FromString(""), + Platform: &ociv1.Platform{ + Architecture: "amd64", + OS: "linux", + }, + }, + }, + }, + }, + Expectation: Expectation{Ref: "docker.io/library/alpine@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + }, + { + Name: "not authorized", + Ref: "registry-1.testing.gitpod-self-hosted.com:5000/gitpod/gitpod/workspace-full:latest", + ResolveResponse: ResolveResponse{ + Error: errors.New("401 Unauthorized"), + }, + Expectation: Expectation{ + Error: resolve.ErrUnauthorized.Error(), + }, + }, + { + Name: "not found", + Ref: "something.com/we/dont:find", + ResolveResponse: ResolveResponse{ + Error: errdefs.ErrNotFound, + }, + Expectation: Expectation{ + Error: resolve.ErrNotFound.Error(), + }, + }, + { + Name: "familiar name", + Ref: "alpine:latest", + ResolveResponse: ResolveResponse{ + NormalisedRef: "docker.io/library/alpine:latest", + Error: errdefs.ErrNotFound, + }, + Expectation: Expectation{ + Error: resolve.ErrNotFound.Error(), + }, + }, + } + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + factory := func() remotes.Resolver { + resolver := NewMockResolver(ctrl) + + resolvedRef := test.ResolveResponse.ResolvedRef + if resolvedRef == "" { + resolvedRef = test.Ref + } + normalizedRef := test.ResolveResponse.NormalisedRef + if normalizedRef == "" { + normalizedRef = test.Ref + } + var respBytes []byte + switch { + case test.ResolveResponse.MF != nil: + var err error + respBytes, err = json.Marshal(test.ResolveResponse.MF) + if err != nil { + t.Fatal(err) + } + + case test.ResolveResponse.Index != nil: + var err error + respBytes, err = json.Marshal(test.ResolveResponse.Index) + if err != nil { + t.Fatal(err) + } + default: + } + if respBytes == nil { + resolver.EXPECT().Resolve(gomock.Any(), gomock.Eq(normalizedRef)).AnyTimes().Return("", ociv1.Descriptor{}, test.ResolveResponse.Error) + } else { + resolver.EXPECT().Resolve(gomock.Any(), gomock.Eq(normalizedRef)).AnyTimes().Return(resolvedRef, ociv1.Descriptor{ + MediaType: ociv1.MediaTypeImageManifest, + Digest: digest.FromBytes(respBytes), + Size: int64(len(respBytes)), + }, nil) + fetcher := NewMockFetcher(ctrl) + fetcher.EXPECT().Fetch(gomock.Any(), gomock.Any()).AnyTimes().Return(io.NopCloser(bytes.NewReader(respBytes)), nil).AnyTimes() + resolver.EXPECT().Fetcher(gomock.Any(), gomock.Any()).AnyTimes().Return(fetcher, nil) + } + + return resolver + } + + sr := &resolve.StandaloneRefResolver{ResolverFactory: factory} + ref, err := sr.Resolve(context.Background(), test.Ref) + act := Expectation{Ref: ref} + if err != nil { + act.Error = err.Error() + } + + if diff := cmp.Diff(test.Expectation, act); diff != "" { + t.Errorf("Resolve() mismatch (-want +got):\n%s", diff) + } + }) + } +} From 3ca8b499abf3a919349676b9b11ae77a2220ab35 Mon Sep 17 00:00:00 2001 From: Christian Weichel Date: Wed, 4 May 2022 19:29:02 +0000 Subject: [PATCH 2/2] [image-builder] Support pre-caching from authenticated registry --- .../image-builder-mk3/pkg/resolve/resolve.go | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/components/image-builder-mk3/pkg/resolve/resolve.go b/components/image-builder-mk3/pkg/resolve/resolve.go index fb1c891b608c4a..fa27ecb6ef15ed 100644 --- a/components/image-builder-mk3/pkg/resolve/resolve.go +++ b/components/image-builder-mk3/pkg/resolve/resolve.go @@ -198,6 +198,7 @@ type DockerRefResolver interface { type PrecachingRefResolver struct { Resolver DockerRefResolver Candidates []string + Auth auth.RegistryAuthenticator mu sync.RWMutex cache map[string]string @@ -218,7 +219,24 @@ func (pr *PrecachingRefResolver) StartCaching(ctx context.Context, interval time pr.cache = make(map[string]string) for { for _, c := range pr.Candidates { - res, err := pr.Resolver.Resolve(ctx, c) + var opts []DockerRefResolverOption + if pr.Auth != nil { + ref, err := reference.ParseNormalizedNamed(c) + if err != nil { + log.WithError(err).WithField("ref", c).Warn("unable to precache reference: cannot parse") + continue + } + + auth, err := pr.Auth.Authenticate(reference.Domain(ref)) + if err != nil { + log.WithError(err).WithField("ref", c).Warn("unable to precache reference: cannot authenticate") + continue + } + + opts = append(opts, WithAuthentication(auth)) + } + + res, err := pr.Resolver.Resolve(ctx, c, opts...) if err != nil { log.WithError(err).WithField("ref", c).Warn("unable to precache reference") continue