github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/cache/lookup_test.go (about)

     1  /*
     2  Copyright 2019 The Skaffold Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package cache
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"reflect"
    24  	"testing"
    25  
    26  	"github.com/docker/docker/client"
    27  	specs "github.com/opencontainers/image-spec/specs-go/v1"
    28  
    29  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
    30  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
    31  	sErrors "github.com/GoogleContainerTools/skaffold/pkg/skaffold/errors"
    32  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
    33  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform"
    34  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
    35  	"github.com/GoogleContainerTools/skaffold/proto/v1"
    36  	"github.com/GoogleContainerTools/skaffold/testutil"
    37  )
    38  
    39  func TestLookupLocal(t *testing.T) {
    40  	tests := []struct {
    41  		description string
    42  		hasher      artifactHasher
    43  		cache       map[string]ImageDetails
    44  		api         *testutil.FakeAPIClient
    45  		expected    cacheDetails
    46  	}{
    47  		{
    48  			description: "miss",
    49  			hasher:      mockHasher{"thehash"},
    50  			api:         &testutil.FakeAPIClient{},
    51  			cache:       map[string]ImageDetails{},
    52  			expected:    needsBuilding{hash: "thehash"},
    53  		},
    54  		{
    55  			description: "hash failure",
    56  			hasher:      failingHasher{errors.New("BUG")},
    57  			expected:    failed{err: errors.New("getting hash for artifact \"artifact\": BUG")},
    58  		},
    59  		{
    60  			description: "miss no imageID",
    61  			hasher:      mockHasher{"hash"},
    62  			cache: map[string]ImageDetails{
    63  				"hash": {Digest: "ignored"},
    64  			},
    65  			expected: needsBuilding{hash: "hash"},
    66  		},
    67  		{
    68  			description: "hit but not found",
    69  			hasher:      mockHasher{"hash"},
    70  			cache: map[string]ImageDetails{
    71  				"hash": {ID: "imageID"},
    72  			},
    73  			api:      &testutil.FakeAPIClient{},
    74  			expected: needsBuilding{hash: "hash"},
    75  		},
    76  		{
    77  			description: "hit but not found with error",
    78  			hasher:      mockHasher{"hash"},
    79  			cache: map[string]ImageDetails{
    80  				"hash": {ID: "imageID"},
    81  			},
    82  			api: &testutil.FakeAPIClient{
    83  				ErrImageInspect: true,
    84  			},
    85  			expected: failed{err: sErrors.NewError(
    86  				fmt.Errorf("getting imageID for tag: "),
    87  				&proto.ActionableErr{
    88  					Message: "getting imageID for tag: ",
    89  					ErrCode: proto.StatusCode_BUILD_DOCKER_GET_DIGEST_ERR,
    90  				})},
    91  		},
    92  		{
    93  			description: "hit",
    94  			hasher:      mockHasher{"hash"},
    95  			cache: map[string]ImageDetails{
    96  				"hash": {ID: "imageID"},
    97  			},
    98  			api:      (&testutil.FakeAPIClient{}).Add("tag", "imageID"),
    99  			expected: found{hash: "hash"},
   100  		},
   101  		{
   102  			description: "hit but different tag",
   103  			hasher:      mockHasher{"hash"},
   104  			cache: map[string]ImageDetails{
   105  				"hash": {ID: "imageID"},
   106  			},
   107  			api:      (&testutil.FakeAPIClient{}).Add("tag", "otherImageID").Add("othertag", "imageID"),
   108  			expected: needsLocalTagging{hash: "hash", tag: "tag", imageID: "imageID"},
   109  		},
   110  		{
   111  			description: "hit but imageID not found",
   112  			hasher:      mockHasher{"hash"},
   113  			cache: map[string]ImageDetails{
   114  				"hash": {ID: "imageID"},
   115  			},
   116  			api:      (&testutil.FakeAPIClient{}).Add("tag", "otherImageID"),
   117  			expected: needsBuilding{hash: "hash"},
   118  		},
   119  	}
   120  	for _, test := range tests {
   121  		testutil.Run(t, test.description, func(t *testutil.T) {
   122  			cache := &cache{
   123  				isLocalImage:       func(string) (bool, error) { return true, nil },
   124  				importMissingImage: func(imageName string) (bool, error) { return false, nil },
   125  				artifactCache:      test.cache,
   126  				client:             fakeLocalDaemon(test.api),
   127  				cfg:                &mockConfig{mode: config.RunModes.Build},
   128  			}
   129  
   130  			t.Override(&newArtifactHasherFunc, func(_ graph.ArtifactGraph, _ DependencyLister, _ config.RunMode) artifactHasher { return test.hasher })
   131  			details := cache.lookupArtifacts(context.Background(), map[string]string{"artifact": "tag"}, platform.Resolver{}, []*latest.Artifact{{
   132  				ImageName: "artifact",
   133  			}})
   134  
   135  			// cmp.Diff cannot access unexported fields in *exec.Cmd, so use reflect.DeepEqual here directly
   136  			if !reflect.DeepEqual(test.expected, details[0]) {
   137  				t.Errorf("Expected result different from actual result. Expected: \n%v, \nActual: \n%v", test.expected, details)
   138  			}
   139  		})
   140  	}
   141  }
   142  
   143  func TestLookupRemote(t *testing.T) {
   144  	tests := []struct {
   145  		description string
   146  		hasher      artifactHasher
   147  		cache       map[string]ImageDetails
   148  		api         *testutil.FakeAPIClient
   149  		expected    cacheDetails
   150  	}{
   151  		{
   152  			description: "miss",
   153  			hasher:      mockHasher{"hash"},
   154  			api:         &testutil.FakeAPIClient{ErrImagePull: true},
   155  			cache:       map[string]ImageDetails{},
   156  			expected:    needsBuilding{hash: "hash"},
   157  		},
   158  		{
   159  			description: "hash failure",
   160  			hasher:      failingHasher{errors.New("BUG")},
   161  			expected:    failed{err: errors.New("getting hash for artifact \"artifact\": BUG")},
   162  		},
   163  		{
   164  			description: "hit",
   165  			hasher:      mockHasher{"hash"},
   166  			cache: map[string]ImageDetails{
   167  				"hash": {Digest: "digest"},
   168  			},
   169  			expected: found{hash: "hash"},
   170  		},
   171  		{
   172  			description: "hit with different tag",
   173  			hasher:      mockHasher{"hash"},
   174  			cache: map[string]ImageDetails{
   175  				"hash": {Digest: "otherdigest"},
   176  			},
   177  			expected: needsRemoteTagging{hash: "hash", tag: "tag", digest: "otherdigest"},
   178  		},
   179  		{
   180  			description: "found locally",
   181  			hasher:      mockHasher{"hash"},
   182  			cache: map[string]ImageDetails{
   183  				"hash": {ID: "imageID"},
   184  			},
   185  			api:      (&testutil.FakeAPIClient{}).Add("tag", "imageID"),
   186  			expected: needsPushing{hash: "hash", tag: "tag", imageID: "imageID"},
   187  		},
   188  		{
   189  			description: "not found",
   190  			hasher:      mockHasher{"hash"},
   191  			cache: map[string]ImageDetails{
   192  				"hash": {ID: "imageID"},
   193  			},
   194  			api:      &testutil.FakeAPIClient{},
   195  			expected: needsBuilding{hash: "hash"},
   196  		},
   197  	}
   198  	for _, test := range tests {
   199  		testutil.Run(t, test.description, func(t *testutil.T) {
   200  			t.Override(&docker.RemoteDigest, func(identifier string, _ docker.Config, _ []specs.Platform) (string, error) {
   201  				switch {
   202  				case identifier == "tag":
   203  					return "digest", nil
   204  				case identifier == "tag@otherdigest":
   205  					return "otherdigest", nil
   206  				default:
   207  					return "", errors.New("unknown remote tag")
   208  				}
   209  			})
   210  
   211  			cache := &cache{
   212  				isLocalImage:       func(string) (bool, error) { return false, nil },
   213  				importMissingImage: func(imageName string) (bool, error) { return false, nil },
   214  				artifactCache:      test.cache,
   215  				client:             fakeLocalDaemon(test.api),
   216  				cfg:                &mockConfig{mode: config.RunModes.Build},
   217  			}
   218  			t.Override(&newArtifactHasherFunc, func(_ graph.ArtifactGraph, _ DependencyLister, _ config.RunMode) artifactHasher { return test.hasher })
   219  			details := cache.lookupArtifacts(context.Background(), map[string]string{"artifact": "tag"}, platform.Resolver{}, []*latest.Artifact{{
   220  				ImageName: "artifact",
   221  			}})
   222  
   223  			// cmp.Diff cannot access unexported fields in *exec.Cmd, so use reflect.DeepEqual here directly
   224  			if !reflect.DeepEqual(test.expected, details[0]) {
   225  				t.Errorf("Expected result different from actual result. Expected: \n%v, \nActual: \n%v", test.expected, details)
   226  			}
   227  		})
   228  	}
   229  }
   230  
   231  type mockHasher struct {
   232  	val string
   233  }
   234  
   235  func (m mockHasher) hash(context.Context, *latest.Artifact, platform.Resolver) (string, error) {
   236  	return m.val, nil
   237  }
   238  
   239  type failingHasher struct {
   240  	err error
   241  }
   242  
   243  func (f failingHasher) hash(context.Context, *latest.Artifact, platform.Resolver) (string, error) {
   244  	return "", f.err
   245  }
   246  
   247  func fakeLocalDaemon(api client.CommonAPIClient) docker.LocalDaemon {
   248  	return docker.NewLocalDaemon(api, nil, false, nil)
   249  }