Path: blob/main/components/image-builder-mk3/pkg/resolve/resolve_test.go
2500 views
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34//go:generate sh -c "mockgen -package resolve_test github.com/containerd/containerd/remotes Resolver,Fetcher > resolve_mock_test.go"5//go:generate leeway run components:update-license-header67package resolve_test89import (10"bytes"11"context"12"encoding/json"13"errors"14"io"15"testing"1617"github.com/containerd/containerd/errdefs"18"github.com/containerd/containerd/remotes"19"github.com/gitpod-io/gitpod/image-builder/pkg/resolve"20"github.com/golang/mock/gomock"21"github.com/google/go-cmp/cmp"22"github.com/google/go-cmp/cmp/cmpopts"23"github.com/opencontainers/go-digest"24ociv1 "github.com/opencontainers/image-spec/specs-go/v1"25)2627func TestStandaloneRefResolverResolve(t *testing.T) {28type Expectation struct {29Ref string30Error string31ErrorMatcher func(error) bool32}33type ResolveResponse struct {34Error error35ResolvedRef string36NormalisedRef string37MF *ociv1.Manifest38Index *ociv1.Index39}40tests := []struct {41Name string42ResolveResponse ResolveResponse43Expectation Expectation44Ref string45}{46{47Name: "basic resolve",48Ref: "docker.io/library/alpine:latest",49ResolveResponse: ResolveResponse{50MF: &ociv1.Manifest{Config: ociv1.Descriptor{MediaType: ociv1.MediaTypeImageManifest, Size: 1}},51},52Expectation: Expectation{53Ref: "docker.io/library/alpine@sha256:839b30716e23e3ac46ed28d6f9fc0f77e3e7121a9b4b2833e8fb6ec4ec89b3bd",54},55},56{57Name: "with-port",58Ref: "some-internal-registry.customer.com:5000/gitpod/base-image:latest",59ResolveResponse: ResolveResponse{60MF: &ociv1.Manifest{Config: ociv1.Descriptor{MediaType: ociv1.MediaTypeImageManifest, Size: 1}},61},62Expectation: Expectation{63Ref: "some-internal-registry.customer.com:5000/gitpod/base-image@sha256:839b30716e23e3ac46ed28d6f9fc0f77e3e7121a9b4b2833e8fb6ec4ec89b3bd",64},65},66{67Name: "with-broken-index",68Ref: "docker.io/library/alpine:latest",69ResolveResponse: ResolveResponse{70Index: &ociv1.Index{71Manifests: []ociv1.Descriptor{72{73MediaType: ociv1.MediaTypeImageManifest,74Digest: digest.FromString(""),75},76},77},78},79Expectation: Expectation{Error: "no manifest for platform linux-amd64 found"},80},81{82Name: "with-working-index",83Ref: "docker.io/library/alpine:latest",84ResolveResponse: ResolveResponse{85Index: &ociv1.Index{86Manifests: []ociv1.Descriptor{87{88MediaType: ociv1.MediaTypeImageManifest,89Digest: digest.FromString(""),90Platform: &ociv1.Platform{91Architecture: "amd64",92OS: "linux",93},94},95},96},97},98Expectation: Expectation{Ref: "docker.io/library/alpine@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},99},100{101Name: "not authorized",102Ref: "registry-1.testing.gitpod-self-hosted.com:5000/gitpod/gitpod/workspace-full:latest",103ResolveResponse: ResolveResponse{104Error: errors.New("401 Unauthorized"),105},106Expectation: Expectation{107Error: resolve.ErrUnauthorized.Error(),108},109},110{111Name: "dockerhub rate limit",112Ref: "registry-1.docker.io:5000/gitpod/gitpod/workspace-full:latest-pulled-too-often",113ResolveResponse: ResolveResponse{114Error: errors.New("httpReadSeeker: failed open: unexpected status code https://registry-1.docker.io/v2/gitpod/workspace-full/manifests/sha256:279f925ad6395f11f6b60e63d7efa5c0b26a853c6052327efbe29bbcc0bafd6a: 429 Too Many Requests - Server message: toomanyrequests: You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit"),115},116Expectation: Expectation{117ErrorMatcher: resolve.TooManyRequestsMatcher,118},119},120{121Name: "not found",122Ref: "something.com/we/dont:find",123ResolveResponse: ResolveResponse{124Error: errdefs.ErrNotFound,125},126Expectation: Expectation{127Error: resolve.ErrNotFound.Error(),128},129},130{131Name: "not found with digest",132Ref: "something.com/we/dont/find@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",133ResolveResponse: ResolveResponse{134Error: errdefs.ErrNotFound,135},136Expectation: Expectation{137Error: resolve.ErrNotFound.Error(),138},139},140{141Name: "familiar name",142Ref: "alpine:latest",143ResolveResponse: ResolveResponse{144NormalisedRef: "docker.io/library/alpine:latest",145Error: errdefs.ErrNotFound,146},147Expectation: Expectation{148Error: resolve.ErrNotFound.Error(),149},150},151}152153for _, test := range tests {154t.Run(test.Name, func(t *testing.T) {155ctrl := gomock.NewController(t)156defer ctrl.Finish()157158factory := func() remotes.Resolver {159resolver := NewMockResolver(ctrl)160161resolvedRef := test.ResolveResponse.ResolvedRef162if resolvedRef == "" {163resolvedRef = test.Ref164}165normalizedRef := test.ResolveResponse.NormalisedRef166if normalizedRef == "" {167normalizedRef = test.Ref168}169var respBytes []byte170switch {171case test.ResolveResponse.MF != nil:172var err error173respBytes, err = json.Marshal(test.ResolveResponse.MF)174if err != nil {175t.Fatal(err)176}177178case test.ResolveResponse.Index != nil:179var err error180respBytes, err = json.Marshal(test.ResolveResponse.Index)181if err != nil {182t.Fatal(err)183}184default:185}186if respBytes == nil {187resolver.EXPECT().Resolve(gomock.Any(), gomock.Eq(normalizedRef)).AnyTimes().Return("", ociv1.Descriptor{}, test.ResolveResponse.Error)188} else {189resolver.EXPECT().Resolve(gomock.Any(), gomock.Eq(normalizedRef)).AnyTimes().Return(resolvedRef, ociv1.Descriptor{190MediaType: ociv1.MediaTypeImageManifest,191Digest: digest.FromBytes(respBytes),192Size: int64(len(respBytes)),193}, nil)194fetcher := NewMockFetcher(ctrl)195fetcher.EXPECT().Fetch(gomock.Any(), gomock.Any()).AnyTimes().Return(io.NopCloser(bytes.NewReader(respBytes)), nil).AnyTimes()196resolver.EXPECT().Fetcher(gomock.Any(), gomock.Any()).AnyTimes().Return(fetcher, nil)197}198199return resolver200}201202sr := &resolve.StandaloneRefResolver{ResolverFactory: factory}203ref, err := sr.Resolve(context.Background(), test.Ref)204act := Expectation{Ref: ref}205if err != nil {206act.Error = err.Error()207}208209// ErrorMatcher?210if err != nil && test.Expectation.ErrorMatcher != nil {211if test.Expectation.ErrorMatcher(err) {212test.Expectation.Error = act.Error213} else {214test.Expectation.Error = "ErrorMatcher failed"215}216}217218if diff := cmp.Diff(test.Expectation, act, cmpopts.IgnoreFields(Expectation{}, "ErrorMatcher")); diff != "" {219t.Errorf("Resolve() mismatch (-want +got):\n%s", diff)220}221})222}223}224225226