diff --git a/api/handlers/distribution/distribution.go b/api/handlers/distribution/distribution.go deleted file mode 100644 index 4670a29..0000000 --- a/api/handlers/distribution/distribution.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package distribution - -import ( - "context" - "fmt" - "net/http" - - "github.com/containerd/containerd/namespaces" - "github.com/containerd/nerdctl/pkg/config" - dockertypes "github.com/docker/cli/cli/config/types" - registrytypes "github.com/docker/docker/api/types/registry" - "github.com/gorilla/mux" - "github.com/runfinch/finch-daemon/api/auth" - "github.com/runfinch/finch-daemon/api/response" - "github.com/runfinch/finch-daemon/api/types" - "github.com/runfinch/finch-daemon/pkg/errdefs" - "github.com/runfinch/finch-daemon/pkg/flog" -) - -//go:generate mockgen --destination=../../../mocks/mocks_distribution/distributionsvc.go -package=mocks_distribution github.com/runfinch/finch-daemon/api/handlers/distribution Service -type Service interface { - Inspect(ctx context.Context, name string, authCfg *dockertypes.AuthConfig) (*registrytypes.DistributionInspect, error) -} - -func RegisterHandlers(r types.VersionedRouter, service Service, conf *config.Config, logger flog.Logger) { - h := newHandler(service, conf, logger) - r.HandleFunc("/distribution/{name}/json", h.inspect, http.MethodGet) -} - -func newHandler(service Service, conf *config.Config, logger flog.Logger) *handler { - return &handler{ - service: service, - Config: conf, - logger: logger, - } -} - -type handler struct { - service Service - Config *config.Config - logger flog.Logger -} - -func (h *handler) inspect(w http.ResponseWriter, r *http.Request) { - name := mux.Vars(r)["name"] - // get auth creds from header - authCfg, err := auth.DecodeAuthConfig(r.Header.Get(auth.AuthHeader)) - if err != nil { - response.SendErrorResponse(w, http.StatusBadRequest, fmt.Errorf("failed to decode auth header: %s", err)) - return - } - ctx := namespaces.WithNamespace(r.Context(), h.Config.Namespace) - inspectRes, err := h.service.Inspect(ctx, name, authCfg) - // map the error into http status code and send response. - if err != nil { - var code int - switch { - case errdefs.IsInvalidFormat(err): - code = http.StatusBadRequest - case errdefs.IsForbiddenError(err): - code = http.StatusForbidden - case errdefs.IsNotFound(err): - code = http.StatusNotFound - default: - code = http.StatusInternalServerError - } - h.logger.Debugf("Inspect Distribution API failed. Status code %d, Message: %s", code, err) - response.SendErrorResponse(w, code, err) - return - } - - // return JSON response - response.JSON(w, http.StatusOK, inspectRes) -} diff --git a/api/handlers/distribution/distribution_test.go b/api/handlers/distribution/distribution_test.go deleted file mode 100644 index e6be297..0000000 --- a/api/handlers/distribution/distribution_test.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package distribution - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/containerd/nerdctl/pkg/config" - registrytypes "github.com/docker/docker/api/types/registry" - "github.com/golang/mock/gomock" - "github.com/gorilla/mux" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" - - "github.com/runfinch/finch-daemon/mocks/mocks_distribution" - "github.com/runfinch/finch-daemon/mocks/mocks_logger" - "github.com/runfinch/finch-daemon/pkg/errdefs" -) - -// TestDistributionHandler function is the entry point of distribution handler package's unit test using ginkgo. -func TestDistributionHandler(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "UnitTests - Distribution APIs Handler") -} - -var _ = Describe("Distribution Inspect API", func() { - var ( - mockCtrl *gomock.Controller - logger *mocks_logger.Logger - service *mocks_distribution.MockService - h *handler - rr *httptest.ResponseRecorder - name string - req *http.Request - ociPlatformAmd ocispec.Platform - ociPlatformArm ocispec.Platform - resp registrytypes.DistributionInspect - respJSON []byte - ) - BeforeEach(func() { - mockCtrl = gomock.NewController(GinkgoT()) - defer mockCtrl.Finish() - logger = mocks_logger.NewLogger(mockCtrl) - service = mocks_distribution.NewMockService(mockCtrl) - c := config.Config{} - h = newHandler(service, &c, logger) - rr = httptest.NewRecorder() - name = "test-image" - var err error - req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("/distribution/%s/json", name), nil) - Expect(err).Should(BeNil()) - req = mux.SetURLVars(req, map[string]string{"name": name}) - ociPlatformAmd = ocispec.Platform{ - Architecture: "amd64", - OS: "linux", - } - ociPlatformArm = ocispec.Platform{ - Architecture: "amd64", - OS: "linux", - } - resp = registrytypes.DistributionInspect{ - Descriptor: ocispec.Descriptor{ - MediaType: ocispec.MediaTypeImageManifest, - Digest: "sha256:9bae60c369e612488c2a089c38737277a4823a3af97ec6866c3b4ad05251bfa5", - Size: 2, - URLs: []string{}, - Annotations: map[string]string{}, - Data: []byte{}, - Platform: &ociPlatformAmd, - }, - Platforms: []ocispec.Platform{ - ociPlatformAmd, - ociPlatformArm, - }, - } - respJSON, err = json.Marshal(resp) - Expect(err).Should(BeNil()) - }) - Context("handler", func() { - It("should return inspect object and 200 status code upon success", func() { - service.EXPECT().Inspect(gomock.Any(), name, gomock.Any()).Return(&resp, nil) - - // handler should return response object with 200 status code - h.inspect(rr, req) - Expect(rr.Body).Should(MatchJSON(respJSON)) - Expect(rr).Should(HaveHTTPStatus(http.StatusOK)) - }) - It("should return 403 status code if image resolution fails due to lack of credentials", func() { - service.EXPECT().Inspect(gomock.Any(), name, gomock.Any()).Return(nil, errdefs.NewForbidden(fmt.Errorf("access denied"))) - logger.EXPECT().Debugf(gomock.Any(), gomock.Any()) - - // handler should return error message with 404 status code - h.inspect(rr, req) - Expect(rr.Body).Should(MatchJSON(`{"message": "access denied"}`)) - Expect(rr).Should(HaveHTTPStatus(http.StatusForbidden)) - }) - It("should return 404 status code if image was not found", func() { - service.EXPECT().Inspect(gomock.Any(), name, gomock.Any()).Return(nil, errdefs.NewNotFound(fmt.Errorf("no such image"))) - logger.EXPECT().Debugf(gomock.Any(), gomock.Any()) - - // handler should return error message with 404 status code - h.inspect(rr, req) - Expect(rr.Body).Should(MatchJSON(`{"message": "no such image"}`)) - Expect(rr).Should(HaveHTTPStatus(http.StatusNotFound)) - }) - It("should return 500 status code if service returns an error message", func() { - service.EXPECT().Inspect(gomock.Any(), name, gomock.Any()).Return(nil, fmt.Errorf("error")) - logger.EXPECT().Debugf(gomock.Any(), gomock.Any()) - - // handler should return error message - h.inspect(rr, req) - Expect(rr.Body).Should(MatchJSON(`{"message": "error"}`)) - Expect(rr).Should(HaveHTTPStatus(http.StatusInternalServerError)) - }) - }) -}) diff --git a/api/router/router.go b/api/router/router.go index e149c31..46c1991 100644 --- a/api/router/router.go +++ b/api/router/router.go @@ -17,7 +17,6 @@ import ( "github.com/runfinch/finch-daemon/api/handlers/builder" "github.com/runfinch/finch-daemon/api/handlers/container" - "github.com/runfinch/finch-daemon/api/handlers/distribution" "github.com/runfinch/finch-daemon/api/handlers/exec" "github.com/runfinch/finch-daemon/api/handlers/image" "github.com/runfinch/finch-daemon/api/handlers/network" @@ -32,15 +31,14 @@ import ( // Options defines the router options to be passed into the handlers. type Options struct { - Config *config.Config - ContainerService container.Service - ImageService image.Service - NetworkService network.Service - SystemService system.Service - BuilderService builder.Service - VolumeService volume.Service - ExecService exec.Service - DistributionService distribution.Service + Config *config.Config + ContainerService container.Service + ImageService image.Service + NetworkService network.Service + SystemService system.Service + BuilderService builder.Service + VolumeService volume.Service + ExecService exec.Service // NerdctlWrapper wraps the interactions with nerdctl to build NerdctlWrapper *backend.NerdctlWrapper @@ -61,7 +59,6 @@ func New(opts *Options) http.Handler { builder.RegisterHandlers(vr, opts.BuilderService, opts.Config, logger, opts.NerdctlWrapper) volume.RegisterHandlers(vr, opts.VolumeService, opts.Config, logger) exec.RegisterHandlers(vr, opts.ExecService, opts.Config, logger) - distribution.RegisterHandlers(vr, opts.DistributionService, opts.Config, logger) return ghandlers.LoggingHandler(os.Stderr, r) } diff --git a/cmd/finch-daemon/router_utils.go b/cmd/finch-daemon/router_utils.go index fa7af47..ceec574 100644 --- a/cmd/finch-daemon/router_utils.go +++ b/cmd/finch-daemon/router_utils.go @@ -17,7 +17,6 @@ import ( "github.com/runfinch/finch-daemon/internal/backend" "github.com/runfinch/finch-daemon/internal/service/builder" "github.com/runfinch/finch-daemon/internal/service/container" - "github.com/runfinch/finch-daemon/internal/service/distribution" "github.com/runfinch/finch-daemon/internal/service/exec" "github.com/runfinch/finch-daemon/internal/service/image" "github.com/runfinch/finch-daemon/internal/service/network" @@ -102,15 +101,14 @@ func createRouterOptions( tarExtractor := archive.NewTarExtractor(ecc.NewExecCmdCreator(), logger) return &router.Options{ - Config: conf, - ContainerService: container.NewService(clientWrapper, ncWrapper, logger, fs, tarCreator, tarExtractor), - ImageService: image.NewService(clientWrapper, ncWrapper, logger), - NetworkService: network.NewService(clientWrapper, ncWrapper, logger), - SystemService: system.NewService(clientWrapper, ncWrapper, logger), - BuilderService: builder.NewService(clientWrapper, ncWrapper, logger, tarExtractor), - VolumeService: volume.NewService(ncWrapper, logger), - ExecService: exec.NewService(clientWrapper, logger), - DistributionService: distribution.NewService(clientWrapper, ncWrapper, logger), - NerdctlWrapper: ncWrapper, + Config: conf, + ContainerService: container.NewService(clientWrapper, ncWrapper, logger, fs, tarCreator, tarExtractor), + ImageService: image.NewService(clientWrapper, ncWrapper, logger), + NetworkService: network.NewService(clientWrapper, ncWrapper, logger), + SystemService: system.NewService(clientWrapper, ncWrapper, logger), + BuilderService: builder.NewService(clientWrapper, ncWrapper, logger, tarExtractor), + VolumeService: volume.NewService(ncWrapper, logger), + ExecService: exec.NewService(clientWrapper, logger), + NerdctlWrapper: ncWrapper, } } diff --git a/internal/service/distribution/distribution.go b/internal/service/distribution/distribution.go deleted file mode 100644 index 604a205..0000000 --- a/internal/service/distribution/distribution.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package distribution - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io" - - containerdimages "github.com/containerd/containerd/images" - dockerresolver "github.com/containerd/containerd/remotes/docker" - cerrdefs "github.com/containerd/errdefs" - "github.com/containerd/nerdctl/pkg/imgutil/dockerconfigresolver" - dockertypes "github.com/docker/cli/cli/config/types" - registrytypes "github.com/docker/docker/api/types/registry" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" - - "github.com/runfinch/finch-daemon/api/handlers/distribution" - "github.com/runfinch/finch-daemon/internal/backend" - "github.com/runfinch/finch-daemon/pkg/errdefs" - "github.com/runfinch/finch-daemon/pkg/flog" - "github.com/runfinch/finch-daemon/pkg/utility/authutility" - "github.com/runfinch/finch-daemon/pkg/utility/imageutility" -) - -type service struct { - client backend.ContainerdClient - nctlImageSvc backend.NerdctlImageSvc - logger flog.Logger -} - -// setting getAuthCredsFunc as a variable to allow mocking this function for unit testing. -var getAuthCredsFunc = authutility.GetAuthCreds - -func NewService(client backend.ContainerdClient, nerdctlImageSvc backend.NerdctlImageSvc, logger flog.Logger) distribution.Service { - return &service{ - client: client, - nctlImageSvc: nerdctlImageSvc, - logger: logger, - } -} - -func (s *service) Inspect(ctx context.Context, name string, ac *dockertypes.AuthConfig) (*registrytypes.DistributionInspect, error) { - // Canonicalize and parse raw image reference as "image:tag" or "image@digest" - rawRef, err := imageutility.Canonicalize(name, "") - if err != nil { - return nil, errdefs.NewInvalidFormat(err) - } - namedRef, refDomain, err := s.client.ParseDockerRef(rawRef) - if err != nil { - return nil, errdefs.NewInvalidFormat(err) - } - - // get auth creds and the corresponding docker remotes resolver - var creds dockerconfigresolver.AuthCreds - if ac != nil { - creds, err = getAuthCredsFunc(refDomain, s.client, *ac) - if err != nil { - return nil, err - } - } - resolver, _, err := s.nctlImageSvc.GetDockerResolver(ctx, refDomain, creds) - if err != nil { - return nil, fmt.Errorf("failed to initialize remotes resolver: %s", err) - } - - _, desc, err := resolver.Resolve(ctx, namedRef) - if err != nil { - // translate error definitions from containerd - switch { - case cerrdefs.IsNotFound(err): - return nil, errdefs.NewNotFound(err) - case errors.Is(err, dockerresolver.ErrInvalidAuthorization): - return nil, errdefs.NewForbidden(err) - default: - return nil, err - } - } - - fetcher, err := resolver.Fetcher(ctx, namedRef) - if err != nil { - return nil, err - } - - rc, err := fetcher.Fetch(ctx, desc) - if err != nil { - switch { - case cerrdefs.IsNotFound(err): - return nil, errdefs.NewNotFound(err) - default: - return nil, err - } - } - - res, err := io.ReadAll(rc) - if err != nil { - return nil, fmt.Errorf("failed to read fetch result: %w", err) - } - - if dgst := desc.Digest.Algorithm().FromBytes(res); dgst != desc.Digest { - return nil, fmt.Errorf("digest mismatch: %s != %s", dgst, desc.Digest) - } - - var platforms []ocispec.Platform - switch { - case desc.MediaType == ocispec.MediaTypeImageManifest || - desc.MediaType == containerdimages.MediaTypeDockerSchema2Manifest: - var manifest ocispec.Manifest - if err := json.Unmarshal(res, &manifest); err != nil { - return nil, fmt.Errorf("failed to unmarshal manifest: %w", err) - } - - // fetch the image to get the platform - rc, err := fetcher.Fetch(ctx, manifest.Config) - if err != nil { - switch { - case cerrdefs.IsNotFound(err): - return nil, errdefs.NewNotFound(err) - default: - return nil, err - } - } - - imageRes, err := io.ReadAll(rc) - if err != nil { - return nil, fmt.Errorf("failed to read image: %w", err) - } - - if dgst := manifest.Config.Digest.Algorithm().FromBytes(imageRes); dgst != manifest.Config.Digest { - return nil, fmt.Errorf("image digest mismatch: %s != %s", dgst, manifest.Config.Digest) - } - - var image ocispec.Image - if err := json.Unmarshal(imageRes, &image); err != nil { - return nil, fmt.Errorf("failed to unmarshal image: %w", err) - } - - platforms = []ocispec.Platform{image.Platform} - case desc.MediaType == ocispec.MediaTypeImageIndex || - desc.MediaType == containerdimages.MediaTypeDockerSchema2ManifestList: - var index ocispec.Index - if err := json.Unmarshal(res, &index); err != nil { - return nil, fmt.Errorf("failed to unmarshal index: %w", err) - } - for _, manifest := range index.Manifests { - platforms = append(platforms, *manifest.Platform) - } - } - - return ®istrytypes.DistributionInspect{ - Descriptor: ocispec.Descriptor{ - MediaType: desc.MediaType, - Digest: desc.Digest, - Size: desc.Size, - URLs: desc.URLs, - Annotations: desc.Annotations, - Data: desc.Data, - Platform: desc.Platform, - ArtifactType: desc.ArtifactType, - }, - Platforms: platforms, - }, nil -} diff --git a/internal/service/distribution/distribution_test.go b/internal/service/distribution/distribution_test.go deleted file mode 100644 index 73bf8a6..0000000 --- a/internal/service/distribution/distribution_test.go +++ /dev/null @@ -1,464 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package distribution - -import ( - "context" - "encoding/json" - "fmt" - "io" - "strings" - "testing" - - "github.com/containerd/nerdctl/pkg/imgutil/dockerconfigresolver" - dockertypes "github.com/docker/cli/cli/config/types" - "github.com/golang/mock/gomock" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - specs "github.com/opencontainers/image-spec/specs-go" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" - - "github.com/runfinch/finch-daemon/internal/backend" - "github.com/runfinch/finch-daemon/mocks/mocks_backend" - "github.com/runfinch/finch-daemon/mocks/mocks_logger" - "github.com/runfinch/finch-daemon/mocks/mocks_remotes" - "github.com/runfinch/finch-daemon/pkg/errdefs" -) - -// TestImageHandler function is the entry point of image service package's unit test using ginkgo. -func TestDistributionService(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "UnitTests - Distribution APIs Service") -} - -// Unit tests related to distribution inspect API. -var _ = Describe("Distribution Inspect API ", func() { - Context("service", func() { - var ( - ctx context.Context - mockCtrl *gomock.Controller - logger *mocks_logger.Logger - cdClient *mocks_backend.MockContainerdClient - ncClient *mocks_backend.MockNerdctlImageSvc - mockResolver *mocks_remotes.MockResolver - mockFetcher *mocks_remotes.MockFetcher - name string - tag string - imageRef string - domain string - ociPlatformAmd ocispec.Platform - ociPlatformArm ocispec.Platform - authCfg dockertypes.AuthConfig - authCreds dockerconfigresolver.AuthCreds - imageIndexDescriptor ocispec.Descriptor - imageDescriptor1 ocispec.Descriptor - imageDescriptor2 ocispec.Descriptor - imageIndex ocispec.Index - image ocispec.Image - imageManifestDescriptor ocispec.Descriptor - imageManifest ocispec.Manifest - imageManifestBytes []byte - imageBytes []byte - imageIndexBytes []byte - s service - ) - BeforeEach(func() { - ctx = context.Background() - // initialize mocks - mockCtrl = gomock.NewController(GinkgoT()) - logger = mocks_logger.NewLogger(mockCtrl) - cdClient = mocks_backend.NewMockContainerdClient(mockCtrl) - ncClient = mocks_backend.NewMockNerdctlImageSvc(mockCtrl) - mockResolver = mocks_remotes.NewMockResolver(mockCtrl) - mockFetcher = mocks_remotes.NewMockFetcher(mockCtrl) - name = "public.ecr.aws/test-image/test-image" - tag = "test-tag" - imageRef = fmt.Sprintf("%s:%s", name, tag) - domain = "public.ecr.aws" - ociPlatformAmd = ocispec.Platform{ - Architecture: "amd64", - OS: "linux", - } - ociPlatformArm = ocispec.Platform{ - Architecture: "amd64", - OS: "linux", - } - authCfg = dockertypes.AuthConfig{ - Username: "test-user", - Password: "test-password", - } - authCreds = func(_ string) (string, string, error) { - return authCfg.Username, authCfg.Password, nil - } - - imageIndexDescriptor = ocispec.Descriptor{ - MediaType: ocispec.MediaTypeImageIndex, - Digest: "sha256:9bae60c369e612488c2a089c38737277a4823a3af97ec6866c3b4ad05251bfa5", - Size: 2, - URLs: []string{}, - Annotations: map[string]string{}, - Data: []byte{}, - Platform: &ociPlatformAmd, - } - - imageDescriptor1 = ocispec.Descriptor{ - MediaType: ocispec.MediaTypeImageManifest, - Digest: "sha256:deadbeef", - Size: 2, - URLs: []string{}, - Annotations: map[string]string{}, - Data: []byte{}, - Platform: &ociPlatformAmd, - } - - imageDescriptor2 = ocispec.Descriptor{ - MediaType: ocispec.MediaTypeImageManifest, - Digest: "sha256:decafbad", - Size: 2, - URLs: []string{}, - Annotations: map[string]string{}, - Data: []byte{}, - Platform: &ociPlatformArm, - } - - imageIndex = ocispec.Index{ - Versioned: specs.Versioned{ - SchemaVersion: 1, - }, - MediaType: ocispec.MediaTypeImageIndex, - Manifests: []ocispec.Descriptor{ - imageDescriptor1, - imageDescriptor2, - }, - } - b, err := json.Marshal(imageIndex) - Expect(err).ShouldNot(HaveOccurred()) - imageIndexBytes = b - - imageManifestDescriptor = ocispec.Descriptor{ - MediaType: ocispec.MediaTypeImageManifest, - Digest: "sha256:9b13590c9a50929020dc76a30ad813e42514a4e34de2f04f5a088f5a1320367c", - Size: 2, - URLs: []string{}, - Annotations: map[string]string{}, - Data: []byte{}, - } - - imageManifest = ocispec.Manifest{ - MediaType: ocispec.MediaTypeImageManifest, - Config: ocispec.Descriptor{ - MediaType: ocispec.MediaTypeImageManifest, - Digest: "sha256:58cc9abebfec4b5ee95157d060207f7bc302516e6d84a0d83a560a1f7ed00e6e", - Size: 2, - URLs: []string{}, - Annotations: map[string]string{}, - Data: []byte{}, - Platform: &ocispec.Platform{}, - ArtifactType: "", - }, - } - b, err = json.Marshal(imageManifest) - Expect(err).ShouldNot(HaveOccurred()) - imageManifestBytes = b - - image = ocispec.Image{ - Platform: ociPlatformAmd, - } - b, err = json.Marshal(image) - Expect(err).ShouldNot(HaveOccurred()) - imageBytes = b - - s = service{ - client: cdClient, - nctlImageSvc: ncClient, - logger: logger, - } - }) - - It("should return an error when canonicalization fails due to invalid input", func() { - inspect, err := s.Inspect(ctx, "sdfsdfsdfsdf:invalid@digest", &authCfg) - Expect(err).Should(HaveOccurred()) - Expect(errdefs.IsInvalidFormat(err)).Should(BeTrue()) - Expect(inspect).Should(BeNil()) - }) - - It("should return an error when ParseDockerRef fails due to invalid input", func() { - cdClient.EXPECT().ParseDockerRef(imageRef).Return( - "", "", fmt.Errorf("parsing failed"), - ) - - inspect, err := s.Inspect(ctx, imageRef, &authCfg) - Expect(err).Should(HaveOccurred()) - Expect(errdefs.IsInvalidFormat(err)).Should(BeTrue()) - Expect(inspect).Should(BeNil()) - }) - - It("should return an error when getAuthCredsFunc fails", func() { - cdClient.EXPECT().ParseDockerRef(imageRef).Return( - imageRef, domain, nil, - ) - expectGetAuthCreds(mockCtrl, domain, authCfg).Return( - nil, fmt.Errorf("invalid credentials"), - ) - - inspect, err := s.Inspect(ctx, imageRef, &authCfg) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("invalid credentials")) - Expect(inspect).Should(BeNil()) - }) - - It("should return an error when getting the docker resolver fails", func() { - cdClient.EXPECT().ParseDockerRef(imageRef).Return( - imageRef, domain, nil, - ) - expectGetAuthCreds(mockCtrl, domain, authCfg).Return( - authCreds, nil, - ) - ncClient.EXPECT().GetDockerResolver(gomock.Any(), domain, gomock.Not(gomock.Nil())).Return( - mockResolver, nil, fmt.Errorf("resolver error"), - ) - - inspect, err := s.Inspect(ctx, imageRef, &authCfg) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("resolver error")) - Expect(inspect).Should(BeNil()) - }) - - It("should return an error when Resolving fails", func() { - cdClient.EXPECT().ParseDockerRef(imageRef).Return( - imageRef, domain, nil, - ) - expectGetAuthCreds(mockCtrl, domain, authCfg).Return( - authCreds, nil, - ) - - ncClient.EXPECT().GetDockerResolver(gomock.Any(), domain, gomock.Not(gomock.Nil())).Return( - mockResolver, nil, nil, - ) - - mockResolver.EXPECT().Resolve(gomock.Any(), imageRef).Return("", ocispec.Descriptor{}, fmt.Errorf("failed to resolve")) - - inspect, err := s.Inspect(ctx, imageRef, &authCfg) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("failed to resolve")) - Expect(inspect).Should(BeNil()) - }) - - It("should return an error when creating Fetcher fails", func() { - cdClient.EXPECT().ParseDockerRef(imageRef).Return( - imageRef, domain, nil, - ) - expectGetAuthCreds(mockCtrl, domain, authCfg).Return( - authCreds, nil, - ) - - ncClient.EXPECT().GetDockerResolver(gomock.Any(), domain, gomock.Not(gomock.Nil())).Return( - mockResolver, nil, nil, - ) - - mockResolver.EXPECT().Resolve(gomock.Any(), imageRef).Return("", imageIndexDescriptor, nil) - mockResolver.EXPECT().Fetcher(gomock.Any(), imageRef).Return(nil, fmt.Errorf("failed to create fetcher")) - - inspect, err := s.Inspect(ctx, imageRef, &authCfg) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("failed to create fetcher")) - Expect(inspect).Should(BeNil()) - }) - - It("should return an error when Fetcher fails to Fetch", func() { - cdClient.EXPECT().ParseDockerRef(imageRef).Return( - imageRef, domain, nil, - ) - expectGetAuthCreds(mockCtrl, domain, authCfg).Return( - authCreds, nil, - ) - - ncClient.EXPECT().GetDockerResolver(gomock.Any(), domain, gomock.Not(gomock.Nil())).Return( - mockResolver, nil, nil, - ) - - mockResolver.EXPECT().Resolve(gomock.Any(), imageRef).Return("", imageIndexDescriptor, nil) - mockResolver.EXPECT().Fetcher(gomock.Any(), imageRef).Return(mockFetcher, nil) - mockFetcher.EXPECT().Fetch(gomock.Any(), imageIndexDescriptor).Return(nil, fmt.Errorf("fetcher failed to fetch")) - - inspect, err := s.Inspect(ctx, imageRef, &authCfg) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("fetcher failed to fetch")) - Expect(inspect).Should(BeNil()) - }) - - It("should return an error when reading manifest fails", func() { - cdClient.EXPECT().ParseDockerRef(imageRef).Return( - imageRef, domain, nil, - ) - expectGetAuthCreds(mockCtrl, domain, authCfg).Return( - authCreds, nil, - ) - - ncClient.EXPECT().GetDockerResolver(gomock.Any(), domain, gomock.Not(gomock.Nil())).Return( - mockResolver, nil, nil, - ) - - mockResolver.EXPECT().Resolve(gomock.Any(), imageRef).Return("", imageIndexDescriptor, nil) - mockResolver.EXPECT().Fetcher(gomock.Any(), imageRef).Return(mockFetcher, nil) - imageIndexRc := io.NopCloser(&mockReader{ - err: fmt.Errorf("failed to read"), - }) - mockFetcher.EXPECT().Fetch(gomock.Any(), imageIndexDescriptor).Return(imageIndexRc, nil) - - inspect, err := s.Inspect(ctx, imageRef, &authCfg) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("failed to read")) - Expect(inspect).Should(BeNil()) - }) - - When("Image index", func() { - It("should return expected response upon success", func() { - cdClient.EXPECT().ParseDockerRef(imageRef).Return( - imageRef, domain, nil, - ) - expectGetAuthCreds(mockCtrl, domain, authCfg).Return( - authCreds, nil, - ) - - ncClient.EXPECT().GetDockerResolver(gomock.Any(), domain, gomock.Not(gomock.Nil())).Return( - mockResolver, nil, nil, - ) - - mockResolver.EXPECT().Resolve(gomock.Any(), imageRef).Return("", imageIndexDescriptor, nil) - mockResolver.EXPECT().Fetcher(gomock.Any(), imageRef).Return(mockFetcher, nil) - imageIndexRc := io.NopCloser(strings.NewReader(string(imageIndexBytes))) - mockFetcher.EXPECT().Fetch(gomock.Any(), imageIndexDescriptor).Return(imageIndexRc, nil) - - inspectRes, err := s.Inspect(ctx, imageRef, &authCfg) - Expect(err).ShouldNot(HaveOccurred()) - Expect(inspectRes).ShouldNot(BeNil()) - Expect(inspectRes.Descriptor).Should(Equal(imageIndexDescriptor)) - Expect(inspectRes.Platforms).Should(HaveLen(2)) - Expect(inspectRes.Platforms).Should(ContainElements(ociPlatformAmd, ociPlatformArm)) - }) - }) - - When("Image", func() { - It("should return expected response upon success", func() { - cdClient.EXPECT().ParseDockerRef(imageRef).Return( - imageRef, domain, nil, - ) - expectGetAuthCreds(mockCtrl, domain, authCfg).Return( - authCreds, nil, - ) - - ncClient.EXPECT().GetDockerResolver(gomock.Any(), domain, gomock.Not(gomock.Nil())).Return( - mockResolver, nil, nil, - ) - - mockResolver.EXPECT().Resolve(gomock.Any(), imageRef).Return("", imageManifestDescriptor, nil) - mockResolver.EXPECT().Fetcher(gomock.Any(), imageRef).Return(mockFetcher, nil) - imageManifestRc := io.NopCloser(strings.NewReader(string(imageManifestBytes))) - mockFetcher.EXPECT().Fetch(gomock.Any(), imageManifestDescriptor).Return(imageManifestRc, nil) - - imageRc := io.NopCloser(strings.NewReader(string(imageBytes))) - // gomock.Any() used for second argument because comparing maps compares addresses, which - // will never be equal due to (un)marshalling - mockFetcher.EXPECT().Fetch(gomock.Any(), gomock.Any()).Return(imageRc, nil) - - inspectRes, err := s.Inspect(ctx, imageRef, &authCfg) - Expect(err).ShouldNot(HaveOccurred()) - Expect(inspectRes).ShouldNot(BeNil()) - Expect(inspectRes.Descriptor).Should(Equal(imageManifestDescriptor)) - Expect(inspectRes.Platforms).Should(HaveLen(1)) - Expect(inspectRes.Platforms).Should(ContainElement(ociPlatformAmd)) - }) - - It("should return an error when image Fetcher fails to Fetch", func() { - cdClient.EXPECT().ParseDockerRef(imageRef).Return( - imageRef, domain, nil, - ) - expectGetAuthCreds(mockCtrl, domain, authCfg).Return( - authCreds, nil, - ) - - ncClient.EXPECT().GetDockerResolver(gomock.Any(), domain, gomock.Not(gomock.Nil())).Return( - mockResolver, nil, nil, - ) - - mockResolver.EXPECT().Resolve(gomock.Any(), imageRef).Return("", imageManifestDescriptor, nil) - mockResolver.EXPECT().Fetcher(gomock.Any(), imageRef).Return(mockFetcher, nil) - imageManifestRc := io.NopCloser(strings.NewReader(string(imageManifestBytes))) - mockFetcher.EXPECT().Fetch(gomock.Any(), imageManifestDescriptor).Return(imageManifestRc, nil) - mockFetcher.EXPECT().Fetch(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("image fetcher failed to fetch")) - - inspect, err := s.Inspect(ctx, imageRef, &authCfg) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("image fetcher failed to fetch")) - Expect(inspect).Should(BeNil()) - }) - - It("should return an error when reading image fails", func() { - cdClient.EXPECT().ParseDockerRef(imageRef).Return( - imageRef, domain, nil, - ) - expectGetAuthCreds(mockCtrl, domain, authCfg).Return( - authCreds, nil, - ) - - ncClient.EXPECT().GetDockerResolver(gomock.Any(), domain, gomock.Not(gomock.Nil())).Return( - mockResolver, nil, nil, - ) - - mockResolver.EXPECT().Resolve(gomock.Any(), imageRef).Return("", imageManifestDescriptor, nil) - mockResolver.EXPECT().Fetcher(gomock.Any(), imageRef).Return(mockFetcher, nil) - imageManifestRc := io.NopCloser(strings.NewReader(string(imageManifestBytes))) - mockFetcher.EXPECT().Fetch(gomock.Any(), imageManifestDescriptor).Return(imageManifestRc, nil) - - imageRc := io.NopCloser(&mockReader{ - err: fmt.Errorf("failed to read image"), - }) - mockFetcher.EXPECT().Fetch(gomock.Any(), gomock.Any()).Return(imageRc, nil) - - inspect, err := s.Inspect(ctx, imageRef, &authCfg) - Expect(err).Should(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("failed to read image")) - Expect(inspect).Should(BeNil()) - }) - }) - }) -}) - -// expectGetAuthCreds creates a new mocked object for getAuthCreds function -// with expected input parameters. -func expectGetAuthCreds(ctrl *gomock.Controller, refDomain string, ac dockertypes.AuthConfig) *mockGetAuthCreds { - return &mockGetAuthCreds{ - expectedDomain: refDomain, - expectedAuth: ac, - ctrl: ctrl, - } -} - -type mockGetAuthCreds struct { - expectedDomain string - expectedAuth dockertypes.AuthConfig - ctrl *gomock.Controller -} - -// Return mocks getAuthCreds function with expected input parameters and returns the passed output values. -func (m *mockGetAuthCreds) Return(creds dockerconfigresolver.AuthCreds, err error) { - m.ctrl.RecordCall(m, "GetAuthCreds", m.expectedDomain, m.expectedAuth) - getAuthCredsFunc = func(domain string, _ backend.ContainerdClient, ac dockertypes.AuthConfig) (dockerconfigresolver.AuthCreds, error) { - m.GetAuthCreds(domain, ac) - return creds, err - } -} - -func (m *mockGetAuthCreds) GetAuthCreds(domain string, ac dockertypes.AuthConfig) { - m.ctrl.Call(m, "GetAuthCreds", domain, ac) -} - -type mockReader struct { - err error -} - -func (m mockReader) Read([]byte) (int, error) { - return 0, m.err -} diff --git a/internal/service/image/image.go b/internal/service/image/image.go index cb0fd00..0fd9666 100644 --- a/internal/service/image/image.go +++ b/internal/service/image/image.go @@ -6,18 +6,19 @@ package image import ( "context" "fmt" + "strings" "github.com/containerd/containerd/images" + "github.com/distribution/reference" "github.com/runfinch/finch-daemon/api/handlers/image" "github.com/runfinch/finch-daemon/internal/backend" "github.com/runfinch/finch-daemon/pkg/errdefs" "github.com/runfinch/finch-daemon/pkg/flog" - "github.com/runfinch/finch-daemon/pkg/utility/authutility" ) // setting getAuthCredsFunc as a variable to allow mocking this function for unit testing. -var getAuthCredsFunc = authutility.GetAuthCreds +var getAuthCredsFunc = (*service).getAuthCreds type service struct { client backend.ContainerdClient @@ -72,3 +73,32 @@ const ( tagDigestPrefix = "sha256:" eventType = "image" ) + +func canonicalize(name, tag string) (string, error) { + if name != "" { + if strings.HasPrefix(tag, tagDigestPrefix) { + name += "@" + tag + } else if tag != "" { + name += ":" + tag + } + } else { + name = tag + } + ref, err := reference.ParseAnyReference(name) + if err != nil { + return "", err + } + if named, ok := ref.(reference.Named); ok && refNeedsTag(ref) { + tagged, err := reference.WithTag(named, defaultTag) + if err == nil { + ref = tagged + } + } + return ref.String(), nil +} + +func refNeedsTag(ref reference.Reference) bool { + _, tagged := ref.(reference.Tagged) + _, digested := ref.(reference.Digested) + return !(tagged || digested) +} diff --git a/internal/service/image/image_test.go b/internal/service/image/image_test.go index c851f7b..00b1f73 100644 --- a/internal/service/image/image_test.go +++ b/internal/service/image/image_test.go @@ -18,7 +18,6 @@ import ( "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/runfinch/finch-daemon/internal/backend" "github.com/runfinch/finch-daemon/mocks/mocks_backend" "github.com/runfinch/finch-daemon/mocks/mocks_logger" "github.com/runfinch/finch-daemon/pkg/errdefs" @@ -152,7 +151,7 @@ type mockGetAuthCreds struct { // Return mocks getAuthCreds function with expected input parameters and returns the passed output values. func (m *mockGetAuthCreds) Return(creds dockerconfigresolver.AuthCreds, err error) { m.ctrl.RecordCall(m, "GetAuthCreds", m.expectedDomain, m.expectedAuth) - getAuthCredsFunc = func(domain string, _ backend.ContainerdClient, ac dockertypes.AuthConfig) (dockerconfigresolver.AuthCreds, error) { + getAuthCredsFunc = func(_ *service, domain string, ac dockertypes.AuthConfig) (dockerconfigresolver.AuthCreds, error) { m.GetAuthCreds(domain, ac) return creds, err } diff --git a/internal/service/image/pull.go b/internal/service/image/pull.go index 8649f68..b8d92fc 100644 --- a/internal/service/image/pull.go +++ b/internal/service/image/pull.go @@ -43,7 +43,7 @@ func (s *service) Pull(ctx context.Context, name, tag, platformStr string, ac *d // get auth creds and the corresponding docker remotes resolver var creds dockerconfigresolver.AuthCreds if ac != nil { - creds, err = getAuthCredsFunc(refDomain, s.client, *ac) + creds, err = getAuthCredsFunc(s, refDomain, *ac) if err != nil { return err } @@ -89,3 +89,59 @@ func toImageRef(name, tag string) string { } return fmt.Sprintf("%s:%s", name, tag) } + +// getAuthCreds returns authentication credentials resolver function from image reference domain and auth config. +func (s *service) getAuthCreds(refDomain string, ac dockertypes.AuthConfig) (dockerconfigresolver.AuthCreds, error) { + // return nil if no credentials specified + if ac.Username == "" && ac.Password == "" && ac.IdentityToken == "" && ac.RegistryToken == "" { + return nil, nil + } + + // domain expected by the authcreds function + // DefaultHost converts "docker.io" to "registry-1.docker.io" + expectedDomain, err := s.client.DefaultDockerHost(refDomain) + if err != nil { + return nil, err + } + + // ensure that server address matches the image reference domain + sa := ac.ServerAddress + if sa != "" { + saHostname := convertToHostname(sa) + // "registry-1.docker.io" can show up as "https://index.docker.io/v1/" in ServerAddress + if expectedDomain == "registry-1.docker.io" { + if saHostname != refDomain && sa != dockerconfigresolver.IndexServer { + return nil, fmt.Errorf("specified server address %s does not match the image reference domain %s", sa, refDomain) + } + } else if saHostname != refDomain { + return nil, fmt.Errorf("specified server address %s does not match the image reference domain %s", sa, refDomain) + } + } + + // return auth creds function + return func(domain string) (string, string, error) { + if domain != expectedDomain { + return "", "", fmt.Errorf("expected domain %s, but got %s", expectedDomain, domain) + } + if ac.IdentityToken != "" { + return "", ac.IdentityToken, nil + } else { + return ac.Username, ac.Password, nil + } + }, nil +} + +// convertToHostname converts a registry url which has http|https prepended +// to just an hostname. +// Copied from github.com/docker/docker/registry.ConvertToHostname to reduce dependencies. +func convertToHostname(url string) string { + stripped := url + if strings.HasPrefix(url, "http://") { + stripped = strings.TrimPrefix(url, "http://") + } else if strings.HasPrefix(url, "https://") { + stripped = strings.TrimPrefix(url, "https://") + } + + hostName, _, _ := strings.Cut(stripped, "/") + return hostName +} diff --git a/internal/service/image/push.go b/internal/service/image/push.go index 5b7f9b2..05112f1 100644 --- a/internal/service/image/push.go +++ b/internal/service/image/push.go @@ -15,12 +15,11 @@ import ( "github.com/runfinch/finch-daemon/api/types" "github.com/runfinch/finch-daemon/pkg/errdefs" - "github.com/runfinch/finch-daemon/pkg/utility/imageutility" ) func (s *service) Push(ctx context.Context, name, tag string, ac *dockertypes.AuthConfig, outStream io.Writer) (*types.PushResult, error) { // Canonicalize and parse raw image reference as "image:tag" or "image@digest" - rawRef, err := imageutility.Canonicalize(name, tag) + rawRef, err := canonicalize(name, tag) if err != nil { return nil, errdefs.NewInvalidFormat(fmt.Errorf("failed to canonicalize the ref: %w", err)) } @@ -48,7 +47,7 @@ func (s *service) Push(ctx context.Context, name, tag string, ac *dockertypes.Au // Get auth creds and the corresponding docker remotes resolver var creds dockerconfigresolver.AuthCreds if ac != nil { - creds, err = getAuthCredsFunc(refDomain, s.client, *ac) + creds, err = getAuthCredsFunc(s, refDomain, *ac) if err != nil { return nil, err } diff --git a/mocks/mocks_distribution/distributionsvc.go b/mocks/mocks_distribution/distributionsvc.go deleted file mode 100644 index 9858d32..0000000 --- a/mocks/mocks_distribution/distributionsvc.go +++ /dev/null @@ -1,52 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/runfinch/finch-daemon/api/handlers/distribution (interfaces: Service) - -// Package mocks_distribution is a generated GoMock package. -package mocks_distribution - -import ( - context "context" - reflect "reflect" - - types "github.com/docker/cli/cli/config/types" - registry "github.com/docker/docker/api/types/registry" - gomock "github.com/golang/mock/gomock" -) - -// MockService is a mock of Service interface. -type MockService struct { - ctrl *gomock.Controller - recorder *MockServiceMockRecorder -} - -// MockServiceMockRecorder is the mock recorder for MockService. -type MockServiceMockRecorder struct { - mock *MockService -} - -// NewMockService creates a new mock instance. -func NewMockService(ctrl *gomock.Controller) *MockService { - mock := &MockService{ctrl: ctrl} - mock.recorder = &MockServiceMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockService) EXPECT() *MockServiceMockRecorder { - return m.recorder -} - -// Inspect mocks base method. -func (m *MockService) Inspect(arg0 context.Context, arg1 string, arg2 *types.AuthConfig) (*registry.DistributionInspect, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Inspect", arg0, arg1, arg2) - ret0, _ := ret[0].(*registry.DistributionInspect) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Inspect indicates an expected call of Inspect. -func (mr *MockServiceMockRecorder) Inspect(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Inspect", reflect.TypeOf((*MockService)(nil).Inspect), arg0, arg1, arg2) -} diff --git a/mocks/mocks_remotes/fetcher.go b/mocks/mocks_remotes/fetcher.go deleted file mode 100644 index 1ce7bbe..0000000 --- a/mocks/mocks_remotes/fetcher.go +++ /dev/null @@ -1,52 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/containerd/containerd/remotes (interfaces: Fetcher) - -// Package mocks_remotes is a generated GoMock package. -package mocks_remotes - -import ( - context "context" - io "io" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - v1 "github.com/opencontainers/image-spec/specs-go/v1" -) - -// 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/mocks/mocks_remotes/resolver.go b/mocks/mocks_remotes/resolver.go deleted file mode 100644 index 8b5b28e..0000000 --- a/mocks/mocks_remotes/resolver.go +++ /dev/null @@ -1,83 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/containerd/containerd/remotes (interfaces: Resolver) - -// Package mocks_remotes is a generated GoMock package. -package mocks_remotes - -import ( - context "context" - 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) -} diff --git a/pkg/utility/authutility/utility.go b/pkg/utility/authutility/utility.go deleted file mode 100644 index b6e67a1..0000000 --- a/pkg/utility/authutility/utility.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package authutility - -import ( - "fmt" - "strings" - - dockertypes "github.com/docker/cli/cli/config/types" - - "github.com/containerd/nerdctl/pkg/imgutil/dockerconfigresolver" - "github.com/runfinch/finch-daemon/internal/backend" -) - -// GetAuthCreds returns authentication credentials resolver function from image reference domain and auth config. -func GetAuthCreds(refDomain string, containerdClient backend.ContainerdClient, ac dockertypes.AuthConfig) (dockerconfigresolver.AuthCreds, error) { - // return nil if no credentials specified - if ac.Username == "" && ac.Password == "" && ac.IdentityToken == "" && ac.RegistryToken == "" { - return nil, nil - } - - // domain expected by the authcreds function - // DefaultHost converts "docker.io" to "registry-1.docker.io" - expectedDomain, err := containerdClient.DefaultDockerHost(refDomain) - if err != nil { - return nil, err - } - - // ensure that server address matches the image reference domain - sa := ac.ServerAddress - if sa != "" { - saHostname := convertToHostname(sa) - // "registry-1.docker.io" can show up as "https://index.docker.io/v1/" in ServerAddress - if expectedDomain == "registry-1.docker.io" { - if saHostname != refDomain && sa != dockerconfigresolver.IndexServer { - return nil, fmt.Errorf("specified server address %s does not match the image reference domain %s", sa, refDomain) - } - } else if saHostname != refDomain { - return nil, fmt.Errorf("specified server address %s does not match the image reference domain %s", sa, refDomain) - } - } - - // return auth creds function - return func(domain string) (string, string, error) { - if domain != expectedDomain { - return "", "", fmt.Errorf("expected domain %s, but got %s", expectedDomain, domain) - } - if ac.IdentityToken != "" { - return "", ac.IdentityToken, nil - } else { - return ac.Username, ac.Password, nil - } - }, nil -} - -// convertToHostname converts a registry url which has http|https prepended -// to just an hostname. -// Copied from github.com/docker/docker/registry.ConvertToHostname to reduce dependencies. -func convertToHostname(url string) string { - stripped := url - if strings.HasPrefix(url, "http://") { - stripped = strings.TrimPrefix(url, "http://") - } else if strings.HasPrefix(url, "https://") { - stripped = strings.TrimPrefix(url, "https://") - } - - hostName, _, _ := strings.Cut(stripped, "/") - return hostName -} diff --git a/pkg/utility/imageutility/utility.go b/pkg/utility/imageutility/utility.go deleted file mode 100644 index dfb5dd3..0000000 --- a/pkg/utility/imageutility/utility.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package imageutility - -import ( - "strings" - - "github.com/distribution/reference" -) - -const ( - defaultTag = "latest" - tagDigestPrefix = "sha256:" - eventType = "image" -) - -func Canonicalize(name, tag string) (string, error) { - if name != "" { - if strings.HasPrefix(tag, tagDigestPrefix) { - name += "@" + tag - } else if tag != "" { - name += ":" + tag - } - } else { - name = tag - } - ref, err := reference.ParseAnyReference(name) - if err != nil { - return "", err - } - if named, ok := ref.(reference.Named); ok && refNeedsTag(ref) { - tagged, err := reference.WithTag(named, defaultTag) - if err == nil { - ref = tagged - } - } - return ref.String(), nil -} - -func refNeedsTag(ref reference.Reference) bool { - _, tagged := ref.(reference.Tagged) - _, digested := ref.(reference.Digested) - return !(tagged || digested) -}