github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/cache/retrieve_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  	"io"
    23  	"io/ioutil"
    24  	"testing"
    25  
    26  	"github.com/docker/docker/api/types"
    27  	specs "github.com/opencontainers/image-spec/specs-go/v1"
    28  
    29  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
    30  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
    31  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
    32  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
    33  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform"
    34  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext"
    35  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
    36  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/tag"
    37  	"github.com/GoogleContainerTools/skaffold/testutil"
    38  )
    39  
    40  func depLister(files map[string][]string) DependencyLister {
    41  	return func(_ context.Context, artifact *latest.Artifact) ([]string, error) {
    42  		list, found := files[artifact.ImageName]
    43  		if !found {
    44  			return nil, errors.New("unknown artifact")
    45  		}
    46  		return list, nil
    47  	}
    48  }
    49  
    50  type mockArtifactStore map[string]string
    51  
    52  func (m mockArtifactStore) GetImageTag(imageName string) (string, bool) { return m[imageName], true }
    53  func (m mockArtifactStore) Record(a *latest.Artifact, tag string)       { m[a.ImageName] = tag }
    54  func (m mockArtifactStore) GetArtifacts([]*latest.Artifact) ([]graph.Artifact, error) {
    55  	return nil, nil
    56  }
    57  
    58  type mockBuilder struct {
    59  	built        []*latest.Artifact
    60  	push         bool
    61  	dockerDaemon docker.LocalDaemon
    62  	store        build.ArtifactStore
    63  }
    64  
    65  func (b *mockBuilder) Build(ctx context.Context, out io.Writer, tags tag.ImageTags, artifacts []*latest.Artifact, _ platform.Resolver) ([]graph.Artifact, error) {
    66  	var built []graph.Artifact
    67  
    68  	for _, artifact := range artifacts {
    69  		b.built = append(b.built, artifact)
    70  		tag := tags[artifact.ImageName]
    71  		opts := docker.BuildOptions{Tag: tag, Mode: config.RunModes.Dev}
    72  		_, err := b.dockerDaemon.Build(ctx, out, artifact.Workspace, artifact.ImageName, artifact.DockerArtifact, opts)
    73  		if err != nil {
    74  			return nil, err
    75  		}
    76  
    77  		if b.push {
    78  			digest, err := b.dockerDaemon.Push(ctx, out, tag)
    79  			if err != nil {
    80  				return nil, err
    81  			}
    82  
    83  			built = append(built, graph.Artifact{
    84  				ImageName: artifact.ImageName,
    85  				Tag:       build.TagWithDigest(tag, digest),
    86  			})
    87  		} else {
    88  			built = append(built, graph.Artifact{
    89  				ImageName: artifact.ImageName,
    90  				Tag:       tag,
    91  			})
    92  			b.store.Record(artifact, tag)
    93  		}
    94  	}
    95  
    96  	return built, nil
    97  }
    98  
    99  type stubAuth struct{}
   100  
   101  func (t stubAuth) GetAuthConfig(string) (types.AuthConfig, error) {
   102  	return types.AuthConfig{}, nil
   103  }
   104  func (t stubAuth) GetAllAuthConfigs(context.Context) (map[string]types.AuthConfig, error) {
   105  	return nil, nil
   106  }
   107  
   108  func TestCacheBuildLocal(t *testing.T) {
   109  	testutil.Run(t, "", func(t *testutil.T) {
   110  		tmpDir := t.NewTempDir().
   111  			Write("dep1", "content1").
   112  			Write("dep2", "content2").
   113  			Write("dep3", "content3").
   114  			Chdir()
   115  
   116  		tags := map[string]string{
   117  			"artifact1": "artifact1:tag1",
   118  			"artifact2": "artifact2:tag2",
   119  		}
   120  		artifacts := []*latest.Artifact{
   121  			{ImageName: "artifact1", ArtifactType: latest.ArtifactType{DockerArtifact: &latest.DockerArtifact{}}},
   122  			{ImageName: "artifact2", ArtifactType: latest.ArtifactType{DockerArtifact: &latest.DockerArtifact{}}},
   123  		}
   124  		deps := depLister(map[string][]string{
   125  			"artifact1": {"dep1", "dep2"},
   126  			"artifact2": {"dep3"},
   127  		})
   128  
   129  		// Mock Docker
   130  		t.Override(&docker.DefaultAuthHelper, stubAuth{})
   131  		dockerDaemon := fakeLocalDaemon(&testutil.FakeAPIClient{})
   132  		t.Override(&docker.NewAPIClient, func(context.Context, docker.Config) (docker.LocalDaemon, error) {
   133  			return dockerDaemon, nil
   134  		})
   135  
   136  		// Mock args builder
   137  		t.Override(&docker.EvalBuildArgs, func(_ config.RunMode, _ string, _ string, args map[string]*string, _ map[string]*string) (map[string]*string, error) {
   138  			return args, nil
   139  		})
   140  
   141  		// Create cache
   142  		cfg := &mockConfig{
   143  			pipeline:  latest.Pipeline{Build: latest.BuildConfig{BuildType: latest.BuildType{LocalBuild: &latest.LocalBuild{TryImportMissing: false}}}},
   144  			cacheFile: tmpDir.Path("cache"),
   145  		}
   146  		store := make(mockArtifactStore)
   147  		artifactCache, err := NewCache(context.Background(), cfg, func(imageName string) (bool, error) { return true, nil }, deps, graph.ToArtifactGraph(artifacts), store)
   148  		t.CheckNoError(err)
   149  
   150  		// First build: Need to build both artifacts
   151  		builder := &mockBuilder{dockerDaemon: dockerDaemon, push: false, store: store}
   152  		bRes, err := artifactCache.Build(context.Background(), ioutil.Discard, tags, artifacts, platform.Resolver{}, builder.Build)
   153  
   154  		t.CheckNoError(err)
   155  		t.CheckDeepEqual(2, len(builder.built))
   156  		t.CheckDeepEqual(2, len(bRes))
   157  
   158  		// Second build: both artifacts are read from cache
   159  		// Artifacts should always be returned in their original order
   160  		builder = &mockBuilder{dockerDaemon: dockerDaemon, push: false, store: store}
   161  		bRes, err = artifactCache.Build(context.Background(), ioutil.Discard, tags, artifacts, platform.Resolver{}, builder.Build)
   162  
   163  		t.CheckNoError(err)
   164  		t.CheckEmpty(builder.built)
   165  		t.CheckDeepEqual(2, len(bRes))
   166  		t.CheckDeepEqual("artifact1", bRes[0].ImageName)
   167  		t.CheckDeepEqual("artifact2", bRes[1].ImageName)
   168  
   169  		// Third build: change first artifact's dependency
   170  		// Artifacts should always be returned in their original order
   171  		tmpDir.Write("dep1", "new content")
   172  		builder = &mockBuilder{dockerDaemon: dockerDaemon, push: false, store: store}
   173  		bRes, err = artifactCache.Build(context.Background(), ioutil.Discard, tags, artifacts, platform.Resolver{}, builder.Build)
   174  
   175  		t.CheckNoError(err)
   176  		t.CheckDeepEqual(1, len(builder.built))
   177  		t.CheckDeepEqual(2, len(bRes))
   178  		t.CheckDeepEqual("artifact1", bRes[0].ImageName)
   179  		t.CheckDeepEqual("artifact2", bRes[1].ImageName)
   180  
   181  		// Fourth build: change second artifact's dependency
   182  		// Artifacts should always be returned in their original order
   183  		tmpDir.Write("dep3", "new content")
   184  		builder = &mockBuilder{dockerDaemon: dockerDaemon, push: false, store: store}
   185  		bRes, err = artifactCache.Build(context.Background(), ioutil.Discard, tags, artifacts, platform.Resolver{}, builder.Build)
   186  
   187  		t.CheckNoError(err)
   188  		t.CheckDeepEqual(1, len(builder.built))
   189  		t.CheckDeepEqual(2, len(bRes))
   190  		t.CheckDeepEqual("artifact1", bRes[0].ImageName)
   191  		t.CheckDeepEqual("artifact2", bRes[1].ImageName)
   192  	})
   193  }
   194  
   195  func TestCacheBuildRemote(t *testing.T) {
   196  	testutil.Run(t, "", func(t *testutil.T) {
   197  		tmpDir := t.NewTempDir().
   198  			Write("dep1", "content1").
   199  			Write("dep2", "content2").
   200  			Write("dep3", "content3").
   201  			Chdir()
   202  
   203  		tags := map[string]string{
   204  			"artifact1": "artifact1:tag1",
   205  			"artifact2": "artifact2:tag2",
   206  		}
   207  		artifacts := []*latest.Artifact{
   208  			{ImageName: "artifact1", ArtifactType: latest.ArtifactType{DockerArtifact: &latest.DockerArtifact{}}},
   209  			{ImageName: "artifact2", ArtifactType: latest.ArtifactType{DockerArtifact: &latest.DockerArtifact{}}},
   210  		}
   211  		deps := depLister(map[string][]string{
   212  			"artifact1": {"dep1", "dep2"},
   213  			"artifact2": {"dep3"},
   214  		})
   215  
   216  		// Mock Docker
   217  		dockerDaemon := fakeLocalDaemon(&testutil.FakeAPIClient{})
   218  		t.Override(&docker.NewAPIClient, func(context.Context, docker.Config) (docker.LocalDaemon, error) {
   219  			return dockerDaemon, nil
   220  		})
   221  		t.Override(&docker.DefaultAuthHelper, stubAuth{})
   222  		t.Override(&docker.RemoteDigest, func(ref string, _ docker.Config, _ []specs.Platform) (string, error) {
   223  			switch ref {
   224  			case "artifact1:tag1":
   225  				return "sha256:51ae7fa00c92525c319404a3a6d400e52ff9372c5a39cb415e0486fe425f3165", nil
   226  			case "artifact2:tag2":
   227  				return "sha256:35bdf2619f59e6f2372a92cb5486f4a0bf9b86e0e89ee0672864db6ed9c51539", nil
   228  			default:
   229  				return "", errors.New("unknown remote tag")
   230  			}
   231  		})
   232  
   233  		// Mock args builder
   234  		t.Override(&docker.EvalBuildArgs, func(_ config.RunMode, _ string, _ string, args map[string]*string, _ map[string]*string) (map[string]*string, error) {
   235  			return args, nil
   236  		})
   237  
   238  		// Create cache
   239  		cfg := &mockConfig{
   240  			pipeline:  latest.Pipeline{Build: latest.BuildConfig{BuildType: latest.BuildType{LocalBuild: &latest.LocalBuild{TryImportMissing: false}}}},
   241  			cacheFile: tmpDir.Path("cache"),
   242  		}
   243  		artifactCache, err := NewCache(context.Background(), cfg, func(imageName string) (bool, error) { return false, nil }, deps, graph.ToArtifactGraph(artifacts), make(mockArtifactStore))
   244  		t.CheckNoError(err)
   245  
   246  		// First build: Need to build both artifacts
   247  		builder := &mockBuilder{dockerDaemon: dockerDaemon, push: true}
   248  		bRes, err := artifactCache.Build(context.Background(), ioutil.Discard, tags, artifacts, platform.Resolver{}, builder.Build)
   249  
   250  		t.CheckNoError(err)
   251  		t.CheckDeepEqual(2, len(builder.built))
   252  		t.CheckDeepEqual(2, len(bRes))
   253  		// Artifacts should always be returned in their original order
   254  		t.CheckDeepEqual("artifact1", bRes[0].ImageName)
   255  		t.CheckDeepEqual("artifact2", bRes[1].ImageName)
   256  
   257  		// Second build: both artifacts are read from cache
   258  		builder = &mockBuilder{dockerDaemon: dockerDaemon, push: true}
   259  		bRes, err = artifactCache.Build(context.Background(), ioutil.Discard, tags, artifacts, platform.Resolver{}, builder.Build)
   260  
   261  		t.CheckNoError(err)
   262  		t.CheckEmpty(builder.built)
   263  		t.CheckDeepEqual(2, len(bRes))
   264  		t.CheckDeepEqual("artifact1", bRes[0].ImageName)
   265  		t.CheckDeepEqual("artifact2", bRes[1].ImageName)
   266  
   267  		// Third build: change one artifact's dependencies
   268  		tmpDir.Write("dep1", "new content")
   269  		builder = &mockBuilder{dockerDaemon: dockerDaemon, push: true}
   270  		bRes, err = artifactCache.Build(context.Background(), ioutil.Discard, tags, artifacts, platform.Resolver{}, builder.Build)
   271  
   272  		t.CheckNoError(err)
   273  		t.CheckDeepEqual(1, len(builder.built))
   274  		t.CheckDeepEqual(2, len(bRes))
   275  		t.CheckDeepEqual("artifact1", bRes[0].ImageName)
   276  		t.CheckDeepEqual("artifact2", bRes[1].ImageName)
   277  	})
   278  }
   279  
   280  func TestCacheFindMissing(t *testing.T) {
   281  	testutil.Run(t, "", func(t *testutil.T) {
   282  		tmpDir := t.NewTempDir().
   283  			Write("dep1", "content1").
   284  			Write("dep2", "content2").
   285  			Write("dep3", "content3").
   286  			Chdir()
   287  
   288  		tags := map[string]string{
   289  			"artifact1": "artifact1:tag1",
   290  			"artifact2": "artifact2:tag2",
   291  		}
   292  		artifacts := []*latest.Artifact{
   293  			{ImageName: "artifact1", ArtifactType: latest.ArtifactType{DockerArtifact: &latest.DockerArtifact{}}},
   294  			{ImageName: "artifact2", ArtifactType: latest.ArtifactType{DockerArtifact: &latest.DockerArtifact{}}},
   295  		}
   296  		deps := depLister(map[string][]string{
   297  			"artifact1": {"dep1", "dep2"},
   298  			"artifact2": {"dep3"},
   299  		})
   300  
   301  		// Mock Docker
   302  		dockerDaemon := fakeLocalDaemon(&testutil.FakeAPIClient{})
   303  		t.Override(&docker.NewAPIClient, func(context.Context, docker.Config) (docker.LocalDaemon, error) {
   304  			return dockerDaemon, nil
   305  		})
   306  		t.Override(&docker.DefaultAuthHelper, stubAuth{})
   307  		t.Override(&docker.RemoteDigest, func(ref string, _ docker.Config, _ []specs.Platform) (string, error) {
   308  			switch ref {
   309  			case "artifact1:tag1":
   310  				return "sha256:51ae7fa00c92525c319404a3a6d400e52ff9372c5a39cb415e0486fe425f3165", nil
   311  			case "artifact2:tag2":
   312  				return "sha256:35bdf2619f59e6f2372a92cb5486f4a0bf9b86e0e89ee0672864db6ed9c51539", nil
   313  			default:
   314  				return "", errors.New("unknown remote tag")
   315  			}
   316  		})
   317  
   318  		// Mock args builder
   319  		t.Override(&docker.EvalBuildArgs, func(_ config.RunMode, _ string, _ string, args map[string]*string, _ map[string]*string) (map[string]*string, error) {
   320  			return args, nil
   321  		})
   322  
   323  		// Create cache
   324  		cfg := &mockConfig{
   325  			pipeline:  latest.Pipeline{Build: latest.BuildConfig{BuildType: latest.BuildType{LocalBuild: &latest.LocalBuild{TryImportMissing: true}}}},
   326  			cacheFile: tmpDir.Path("cache"),
   327  		}
   328  		artifactCache, err := NewCache(context.Background(), cfg, func(imageName string) (bool, error) { return false, nil }, deps, graph.ToArtifactGraph(artifacts), make(mockArtifactStore))
   329  		t.CheckNoError(err)
   330  
   331  		// Because the artifacts are in the docker registry, we expect them to be imported correctly.
   332  		builder := &mockBuilder{dockerDaemon: dockerDaemon, push: false, store: make(mockArtifactStore)}
   333  		bRes, err := artifactCache.Build(context.Background(), ioutil.Discard, tags, artifacts, platform.Resolver{}, builder.Build)
   334  
   335  		t.CheckNoError(err)
   336  		t.CheckDeepEqual(0, len(builder.built))
   337  		t.CheckDeepEqual(2, len(bRes))
   338  		// Artifacts should always be returned in their original order
   339  		t.CheckDeepEqual("artifact1", bRes[0].ImageName)
   340  		t.CheckDeepEqual("artifact2", bRes[1].ImageName)
   341  	})
   342  }
   343  
   344  type mockConfig struct {
   345  	runcontext.RunContext // Embedded to provide the default values.
   346  	cacheFile             string
   347  	mode                  config.RunMode
   348  	pipeline              latest.Pipeline
   349  }
   350  
   351  func (c *mockConfig) CacheArtifacts() bool                            { return true }
   352  func (c *mockConfig) CacheFile() string                               { return c.cacheFile }
   353  func (c *mockConfig) Mode() config.RunMode                            { return c.mode }
   354  func (c *mockConfig) PipelineForImage(string) (latest.Pipeline, bool) { return c.pipeline, true }