diff --git a/docs/content/en/docs/pipeline-stages/builders/docker.md b/docs/content/en/docs/pipeline-stages/builders/docker.md index ad8c0ad5afd..ea68134f2dc 100644 --- a/docs/content/en/docs/pipeline-stages/builders/docker.md +++ b/docs/content/en/docs/pipeline-stages/builders/docker.md @@ -33,6 +33,10 @@ of `skaffold.yaml`. The following options can optionally be configured: {{< schema root="LocalBuild" >}} +The `docker` builder replaces cache references to the +artifact image with the tagged image to allow caching from the +previously built image. + **Example** The following `build` section instructs Skaffold to build a diff --git a/docs/content/en/samples/builders/local.yaml b/docs/content/en/samples/builders/local.yaml index 30e684ab629..58f433c11fd 100644 --- a/docs/content/en/samples/builders/local.yaml +++ b/docs/content/en/samples/builders/local.yaml @@ -1,4 +1,9 @@ build: artifacts: - image: gcr.io/k8s-skaffold/example + docker: + cacheFrom: + # Local Docker builder replaces cache references to the artifact image with + # the tagged image reference, useful for caching from the previous build. + - gcr.io/k8s-skaffold/example local: {} diff --git a/pkg/skaffold/build/docker/docker.go b/pkg/skaffold/build/docker/docker.go index f0c72035580..ca29e43aa21 100644 --- a/pkg/skaffold/build/docker/docker.go +++ b/pkg/skaffold/build/docker/docker.go @@ -28,10 +28,12 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/output" latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util/stringslice" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/warnings" ) func (b *Builder) Build(ctx context.Context, out io.Writer, a *latestV1.Artifact, tag string) (string, error) { + a = adjustCacheFrom(a, tag) instrumentation.AddAttributesToCurrentSpanFromContext(ctx, map[string]string{ "BuildType": "docker", "Context": instrumentation.PII(a.Workspace), @@ -132,3 +134,27 @@ func (b *Builder) pullCacheFromImages(ctx context.Context, out io.Writer, a *lat return nil } + +// adjustCacheFrom returns an artifact where any cache references from the artifactImage is changed to the tagged built image name instead. +func adjustCacheFrom(a *latestV1.Artifact, artifactTag string) *latestV1.Artifact { + if os.Getenv("SKAFFOLD_DISABLE_DOCKER_CACHE_ADJUSTMENT") != "" { + // allow this behaviour to be disabled + return a + } + + if !stringslice.Contains(a.DockerArtifact.CacheFrom, a.ImageName) { + return a + } + + cf := make([]string, 0, len(a.DockerArtifact.CacheFrom)) + for _, image := range a.DockerArtifact.CacheFrom { + if image == a.ImageName { + cf = append(cf, artifactTag) + } else { + cf = append(cf, image) + } + } + copy := *a + copy.DockerArtifact.CacheFrom = cf + return © +} diff --git a/pkg/skaffold/build/docker/docker_test.go b/pkg/skaffold/build/docker/docker_test.go index f289f0ee8f8..a32593169e3 100644 --- a/pkg/skaffold/build/docker/docker_test.go +++ b/pkg/skaffold/build/docker/docker_test.go @@ -21,6 +21,7 @@ import ( "fmt" "io/ioutil" "path/filepath" + "strings" "testing" "github.com/docker/docker/api/types" @@ -178,6 +179,64 @@ func TestDockerCLIBuild(t *testing.T) { } } +func TestDockerCLICheckCacheFromArgs(t *testing.T) { + tests := []struct { + description string + artifact *latestV1.Artifact + tag string + expectedCacheFrom []string + }{ + { + description: "multiple cache-from images", + artifact: &latestV1.Artifact{ + ImageName: "gcr.io/k8s-skaffold/test", + ArtifactType: latestV1.ArtifactType{ + DockerArtifact: &latestV1.DockerArtifact{ + CacheFrom: []string{"from/image1", "from/image2"}, + }, + }, + }, + tag: "tag", + expectedCacheFrom: []string{"from/image1", "from/image2"}, + }, + { + description: "cache-from self uses tagged image", + artifact: &latestV1.Artifact{ + ImageName: "gcr.io/k8s-skaffold/test", + ArtifactType: latestV1.ArtifactType{ + DockerArtifact: &latestV1.DockerArtifact{ + CacheFrom: []string{"gcr.io/k8s-skaffold/test"}, + }, + }, + }, + tag: "gcr.io/k8s-skaffold/test:tagged", + expectedCacheFrom: []string{"gcr.io/k8s-skaffold/test:tagged"}, + }, + } + + for _, test := range tests { + testutil.Run(t, test.description, func(t *testutil.T) { + t.NewTempDir().Touch("Dockerfile").Chdir() + dockerfilePath, _ := filepath.Abs("Dockerfile") + a := *test.artifact + a.Workspace = "." + a.DockerArtifact.DockerfilePath = dockerfilePath + t.Override(&docker.EvalBuildArgs, func(_ config.RunMode, _ string, _ string, args map[string]*string, _ map[string]*string) (map[string]*string, error) { + return args, nil + }) + + mockCmd := testutil.CmdRun( + "docker build . --file " + dockerfilePath + " -t " + test.tag + " --cache-from " + strings.Join(test.expectedCacheFrom, " --cache-from "), + ) + t.Override(&util.DefaultExecCommand, mockCmd) + + builder := NewArtifactBuilder(fakeLocalDaemonWithExtraEnv([]string{}), mockConfig{}, true, util.BoolPtr(false), false, mockArtifactResolver{make(map[string]string)}, nil) + _, err := builder.Build(context.Background(), ioutil.Discard, &a, test.tag) + t.CheckNoError(err) + }) + } +} + func fakeLocalDaemonWithExtraEnv(extraEnv []string) docker.LocalDaemon { return docker.NewLocalDaemon(&testutil.FakeAPIClient{}, extraEnv, false, nil) }