From e6c68726551a529713b7decb274c14e803524df6 Mon Sep 17 00:00:00 2001 From: Guinevere Saenger Date: Tue, 4 Apr 2023 11:38:10 -0700 Subject: [PATCH] Support additional .dockerignore formats (#579) * Add function to map additional dockerignore files. Docker maps "Dockerfile" to a ".dockerignore" file, and it maps "Dockerfile-prod" to "Dockerfile-prod.dockerignore". This commit adds a function and unit tests to map the Docker project's dockerignore file to its expected location. * Add integration tests for .dockerignore While these tests cannot precisely verify whether or not a file was correctly ignored, the docker image being build can expect the existence/nonexistence of a file in the context tarball and fail accordingly. * pacify linter * remove experimental test --- examples/dockerfile_test.go | 68 +++++++++++++++++++ .../dockerignore-default-fail/.dockerignore | 1 + .../dockerignore-default-fail/Dockerfile | 8 +++ .../dockerignore-default-fail/Pulumi.yaml | 13 ++++ .../dockerignore-default-fail/app.txt | 1 + .../dockerignore-default-fail/ignore.txt | 1 + .../dockerignore-default/.dockerignore | 1 + .../dockerignore-default/Dockerfile | 3 + .../dockerignore-default/Pulumi.yaml | 13 ++++ .../dockerignore-default/app.txt | 1 + .../dockerignore-default/ignore.txt | 1 + .../dockerignore-no-mapping/.dockerignore | 1 + .../dockerignore-no-mapping/Mockerfile | 7 ++ .../dockerignore-no-mapping/Pulumi.yaml | 16 +++++ .../dockerignore-no-mapping/app.txt | 1 + .../dockerignore-no-mapping/ignore.txt | 1 + .../dockerignore-specified/Mockerfile | 3 + .../Mockerfile.dockerignore | 1 + .../dockerignore-specified/Pulumi.yaml | 16 +++++ .../dockerignore-specified/app.txt | 1 + .../dockerignore-specified/ignore.txt | 1 + .../Dockerfile | 5 ++ .../Pulumi.yaml | 16 +++++ .../app/.dockerignore | 1 + .../app/app.txt | 1 + .../app/ignore.txt | 0 provider/image.go | 21 +++++- provider/image_test.go | 17 +++++ provider/provider.go | 2 +- 29 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 examples/test-dockerfile/dockerignore-default-fail/.dockerignore create mode 100644 examples/test-dockerfile/dockerignore-default-fail/Dockerfile create mode 100644 examples/test-dockerfile/dockerignore-default-fail/Pulumi.yaml create mode 100644 examples/test-dockerfile/dockerignore-default-fail/app.txt create mode 100644 examples/test-dockerfile/dockerignore-default-fail/ignore.txt create mode 100644 examples/test-dockerfile/dockerignore-default/.dockerignore create mode 100644 examples/test-dockerfile/dockerignore-default/Dockerfile create mode 100644 examples/test-dockerfile/dockerignore-default/Pulumi.yaml create mode 100644 examples/test-dockerfile/dockerignore-default/app.txt create mode 100644 examples/test-dockerfile/dockerignore-default/ignore.txt create mode 100644 examples/test-dockerfile/dockerignore-no-mapping/.dockerignore create mode 100644 examples/test-dockerfile/dockerignore-no-mapping/Mockerfile create mode 100644 examples/test-dockerfile/dockerignore-no-mapping/Pulumi.yaml create mode 100644 examples/test-dockerfile/dockerignore-no-mapping/app.txt create mode 100644 examples/test-dockerfile/dockerignore-no-mapping/ignore.txt create mode 100644 examples/test-dockerfile/dockerignore-specified/Mockerfile create mode 100644 examples/test-dockerfile/dockerignore-specified/Mockerfile.dockerignore create mode 100644 examples/test-dockerfile/dockerignore-specified/Pulumi.yaml create mode 100644 examples/test-dockerfile/dockerignore-specified/app.txt create mode 100644 examples/test-dockerfile/dockerignore-specified/ignore.txt create mode 100644 examples/test-dockerfile/dockerignore-with-external-dockerfile/Dockerfile create mode 100644 examples/test-dockerfile/dockerignore-with-external-dockerfile/Pulumi.yaml create mode 100644 examples/test-dockerfile/dockerignore-with-external-dockerfile/app/.dockerignore create mode 100644 examples/test-dockerfile/dockerignore-with-external-dockerfile/app/app.txt create mode 100644 examples/test-dockerfile/dockerignore-with-external-dockerfile/app/ignore.txt diff --git a/examples/dockerfile_test.go b/examples/dockerfile_test.go index 2773a2e4..601bc53e 100644 --- a/examples/dockerfile_test.go +++ b/examples/dockerfile_test.go @@ -75,3 +75,71 @@ func TestDockerfileInContextYAML(t *testing.T) { SkipRefresh: true, }) } + +func TestDockerignoreDefaultYAML(t *testing.T) { + cwd, err := os.Getwd() + if !assert.NoError(t, err) { + t.FailNow() + } + + integration.ProgramTest(t, &integration.ProgramTestOptions{ + Dir: path.Join(cwd, "test-dockerfile", "dockerignore-default"), + Quick: true, + SkipRefresh: true, + }) +} + +func TestDockerignoreSpecifiedYAML(t *testing.T) { + cwd, err := os.Getwd() + if !assert.NoError(t, err) { + t.FailNow() + } + + integration.ProgramTest(t, &integration.ProgramTestOptions{ + Dir: path.Join(cwd, "test-dockerfile", "dockerignore-specified"), + Quick: true, + SkipRefresh: true, + }) +} + +func TestDockerignoreDefaultFailYAML(t *testing.T) { + cwd, err := os.Getwd() + if !assert.NoError(t, err) { + t.FailNow() + } + + integration.ProgramTest(t, &integration.ProgramTestOptions{ + Dir: path.Join(cwd, "test-dockerfile", "dockerignore-default-fail"), + Quick: true, + SkipRefresh: true, + ExpectFailure: true, + }) +} + +func TestDockerignoreNoMappingYAML(t *testing.T) { + cwd, err := os.Getwd() + if !assert.NoError(t, err) { + t.FailNow() + } + // we expect this test to succeed, as we test that the ignore.txt file does in fact _not_ get ignored + // the ignore.txt file does not get ignored, as .dockerignore does not map to Mockerfile. + // The RUN command in Mockerfile therefore succeeds. + integration.ProgramTest(t, &integration.ProgramTestOptions{ + Dir: path.Join(cwd, "test-dockerfile", "dockerignore-no-mapping"), + Quick: true, + SkipRefresh: true, + }) +} + +func TestDockerignoreWithExternalDockerfileYAML(t *testing.T) { + cwd, err := os.Getwd() + if !assert.NoError(t, err) { + t.FailNow() + } + + integration.ProgramTest(t, &integration.ProgramTestOptions{ + Dir: path.Join(cwd, "test-dockerfile", "dockerignore-with-external-dockerfile"), + Quick: true, + SkipRefresh: true, + }) +} diff --git a/examples/test-dockerfile/dockerignore-default-fail/.dockerignore b/examples/test-dockerfile/dockerignore-default-fail/.dockerignore new file mode 100644 index 00000000..8ea087a8 --- /dev/null +++ b/examples/test-dockerfile/dockerignore-default-fail/.dockerignore @@ -0,0 +1 @@ +ignore.txt diff --git a/examples/test-dockerfile/dockerignore-default-fail/Dockerfile b/examples/test-dockerfile/dockerignore-default-fail/Dockerfile new file mode 100644 index 00000000..3d830886 --- /dev/null +++ b/examples/test-dockerfile/dockerignore-default-fail/Dockerfile @@ -0,0 +1,8 @@ +FROM --platform=linux/amd64 ubuntu + +COPY . / + +RUN cat app.txt + +Run cat ignore.txt + diff --git a/examples/test-dockerfile/dockerignore-default-fail/Pulumi.yaml b/examples/test-dockerfile/dockerignore-default-fail/Pulumi.yaml new file mode 100644 index 00000000..519a9842 --- /dev/null +++ b/examples/test-dockerfile/dockerignore-default-fail/Pulumi.yaml @@ -0,0 +1,13 @@ +name: dockerfile-default +runtime: yaml +resources: + demo-image: + type: docker:Image + properties: + imageName: pulumibot/ignore-image:tag2 + skipPush: true + options: + version: v4.0.0 +outputs: + imageName: ${demo-image.imageName} + out-dockerfile: ${demo-image.dockerfile} diff --git a/examples/test-dockerfile/dockerignore-default-fail/app.txt b/examples/test-dockerfile/dockerignore-default-fail/app.txt new file mode 100644 index 00000000..5adb594f --- /dev/null +++ b/examples/test-dockerfile/dockerignore-default-fail/app.txt @@ -0,0 +1 @@ +Hi! I am a test file! diff --git a/examples/test-dockerfile/dockerignore-default-fail/ignore.txt b/examples/test-dockerfile/dockerignore-default-fail/ignore.txt new file mode 100644 index 00000000..a053a4ea --- /dev/null +++ b/examples/test-dockerfile/dockerignore-default-fail/ignore.txt @@ -0,0 +1 @@ +Hi! I should get ignored! diff --git a/examples/test-dockerfile/dockerignore-default/.dockerignore b/examples/test-dockerfile/dockerignore-default/.dockerignore new file mode 100644 index 00000000..8ea087a8 --- /dev/null +++ b/examples/test-dockerfile/dockerignore-default/.dockerignore @@ -0,0 +1 @@ +ignore.txt diff --git a/examples/test-dockerfile/dockerignore-default/Dockerfile b/examples/test-dockerfile/dockerignore-default/Dockerfile new file mode 100644 index 00000000..de2d80e5 --- /dev/null +++ b/examples/test-dockerfile/dockerignore-default/Dockerfile @@ -0,0 +1,3 @@ +FROM scratch + +COPY . / diff --git a/examples/test-dockerfile/dockerignore-default/Pulumi.yaml b/examples/test-dockerfile/dockerignore-default/Pulumi.yaml new file mode 100644 index 00000000..c5adb5ea --- /dev/null +++ b/examples/test-dockerfile/dockerignore-default/Pulumi.yaml @@ -0,0 +1,13 @@ +name: dockerfile-default +runtime: yaml +resources: + demo-image: + type: docker:Image + properties: + imageName: pulumibot/ignore-image:tag1 + skipPush: true + options: + version: v4.0.0 +outputs: + imageName: ${demo-image.imageName} + out-dockerfile: ${demo-image.dockerfile} diff --git a/examples/test-dockerfile/dockerignore-default/app.txt b/examples/test-dockerfile/dockerignore-default/app.txt new file mode 100644 index 00000000..5adb594f --- /dev/null +++ b/examples/test-dockerfile/dockerignore-default/app.txt @@ -0,0 +1 @@ +Hi! I am a test file! diff --git a/examples/test-dockerfile/dockerignore-default/ignore.txt b/examples/test-dockerfile/dockerignore-default/ignore.txt new file mode 100644 index 00000000..a053a4ea --- /dev/null +++ b/examples/test-dockerfile/dockerignore-default/ignore.txt @@ -0,0 +1 @@ +Hi! I should get ignored! diff --git a/examples/test-dockerfile/dockerignore-no-mapping/.dockerignore b/examples/test-dockerfile/dockerignore-no-mapping/.dockerignore new file mode 100644 index 00000000..8ea087a8 --- /dev/null +++ b/examples/test-dockerfile/dockerignore-no-mapping/.dockerignore @@ -0,0 +1 @@ +ignore.txt diff --git a/examples/test-dockerfile/dockerignore-no-mapping/Mockerfile b/examples/test-dockerfile/dockerignore-no-mapping/Mockerfile new file mode 100644 index 00000000..23a78d71 --- /dev/null +++ b/examples/test-dockerfile/dockerignore-no-mapping/Mockerfile @@ -0,0 +1,7 @@ +FROM --platform=linux/amd64 ubuntu + +COPY . / + +Run cat app.txt + +RUN cat ignore.txt diff --git a/examples/test-dockerfile/dockerignore-no-mapping/Pulumi.yaml b/examples/test-dockerfile/dockerignore-no-mapping/Pulumi.yaml new file mode 100644 index 00000000..03fb542e --- /dev/null +++ b/examples/test-dockerfile/dockerignore-no-mapping/Pulumi.yaml @@ -0,0 +1,16 @@ +name: dockerfile-default +runtime: yaml +resources: + demo-image: + type: docker:Image + properties: + imageName: pulumibot/ignore-image:tag4 + skipPush: true + build: + context: . + dockerfile: Mockerfile + options: + version: v4.0.0 +outputs: + imageName: ${demo-image.imageName} + out-dockerfile: ${demo-image.dockerfile} diff --git a/examples/test-dockerfile/dockerignore-no-mapping/app.txt b/examples/test-dockerfile/dockerignore-no-mapping/app.txt new file mode 100644 index 00000000..5adb594f --- /dev/null +++ b/examples/test-dockerfile/dockerignore-no-mapping/app.txt @@ -0,0 +1 @@ +Hi! I am a test file! diff --git a/examples/test-dockerfile/dockerignore-no-mapping/ignore.txt b/examples/test-dockerfile/dockerignore-no-mapping/ignore.txt new file mode 100644 index 00000000..a053a4ea --- /dev/null +++ b/examples/test-dockerfile/dockerignore-no-mapping/ignore.txt @@ -0,0 +1 @@ +Hi! I should get ignored! diff --git a/examples/test-dockerfile/dockerignore-specified/Mockerfile b/examples/test-dockerfile/dockerignore-specified/Mockerfile new file mode 100644 index 00000000..8c1b5d95 --- /dev/null +++ b/examples/test-dockerfile/dockerignore-specified/Mockerfile @@ -0,0 +1,3 @@ +FROM --platform=linux/amd64 ubuntu + +COPY . / diff --git a/examples/test-dockerfile/dockerignore-specified/Mockerfile.dockerignore b/examples/test-dockerfile/dockerignore-specified/Mockerfile.dockerignore new file mode 100644 index 00000000..8ea087a8 --- /dev/null +++ b/examples/test-dockerfile/dockerignore-specified/Mockerfile.dockerignore @@ -0,0 +1 @@ +ignore.txt diff --git a/examples/test-dockerfile/dockerignore-specified/Pulumi.yaml b/examples/test-dockerfile/dockerignore-specified/Pulumi.yaml new file mode 100644 index 00000000..16cb785a --- /dev/null +++ b/examples/test-dockerfile/dockerignore-specified/Pulumi.yaml @@ -0,0 +1,16 @@ +name: dockerfile-default +runtime: yaml +resources: + demo-image: + type: docker:Image + properties: + imageName: pulumibot/ignore-image:tag3 + skipPush: true + build: + context: . + dockerfile: Mockerfile + options: + version: v4.0.0 +outputs: + imageName: ${demo-image.imageName} + out-dockerfile: ${demo-image.dockerfile} diff --git a/examples/test-dockerfile/dockerignore-specified/app.txt b/examples/test-dockerfile/dockerignore-specified/app.txt new file mode 100644 index 00000000..5adb594f --- /dev/null +++ b/examples/test-dockerfile/dockerignore-specified/app.txt @@ -0,0 +1 @@ +Hi! I am a test file! diff --git a/examples/test-dockerfile/dockerignore-specified/ignore.txt b/examples/test-dockerfile/dockerignore-specified/ignore.txt new file mode 100644 index 00000000..a053a4ea --- /dev/null +++ b/examples/test-dockerfile/dockerignore-specified/ignore.txt @@ -0,0 +1 @@ +Hi! I should get ignored! diff --git a/examples/test-dockerfile/dockerignore-with-external-dockerfile/Dockerfile b/examples/test-dockerfile/dockerignore-with-external-dockerfile/Dockerfile new file mode 100644 index 00000000..e27f42b0 --- /dev/null +++ b/examples/test-dockerfile/dockerignore-with-external-dockerfile/Dockerfile @@ -0,0 +1,5 @@ +FROM --platform=linux/amd64 ubuntu + +COPY . / + +RUN cat app.txt diff --git a/examples/test-dockerfile/dockerignore-with-external-dockerfile/Pulumi.yaml b/examples/test-dockerfile/dockerignore-with-external-dockerfile/Pulumi.yaml new file mode 100644 index 00000000..f2da5706 --- /dev/null +++ b/examples/test-dockerfile/dockerignore-with-external-dockerfile/Pulumi.yaml @@ -0,0 +1,16 @@ +name: dockerfile-external +runtime: yaml +resources: + demo-image: + type: docker:Image + properties: + imageName: pulumibot/test-image:tag3 + skipPush: true + build: + context: ./app + dockerfile: ./Dockerfile + options: + version: v4.0.0 +outputs: + imageName: ${demo-image.imageName} + out-dockerfile: ${demo-image.dockerfile} diff --git a/examples/test-dockerfile/dockerignore-with-external-dockerfile/app/.dockerignore b/examples/test-dockerfile/dockerignore-with-external-dockerfile/app/.dockerignore new file mode 100644 index 00000000..8ea087a8 --- /dev/null +++ b/examples/test-dockerfile/dockerignore-with-external-dockerfile/app/.dockerignore @@ -0,0 +1 @@ +ignore.txt diff --git a/examples/test-dockerfile/dockerignore-with-external-dockerfile/app/app.txt b/examples/test-dockerfile/dockerignore-with-external-dockerfile/app/app.txt new file mode 100644 index 00000000..5adb594f --- /dev/null +++ b/examples/test-dockerfile/dockerignore-with-external-dockerfile/app/app.txt @@ -0,0 +1 @@ +Hi! I am a test file! diff --git a/examples/test-dockerfile/dockerignore-with-external-dockerfile/app/ignore.txt b/examples/test-dockerfile/dockerignore-with-external-dockerfile/app/ignore.txt new file mode 100644 index 00000000..e69de29b diff --git a/provider/image.go b/provider/image.go index 7ccb8938..d52925ca 100644 --- a/provider/image.go +++ b/provider/image.go @@ -118,7 +118,10 @@ func (p *dockerNativeProvider) dockerBuild(ctx context.Context, } // make the build context and ensure to exclude dockerignore file patterns - dockerIgnorePath := filepath.Join(build.Context, ".dockerignore") + // map the expected location for dockerignore + dockerignore := mapDockerignore(filepath.Base(build.Dockerfile)) + dockerIgnorePath := filepath.Join(build.Context, dockerignore) + initialIgnorePatterns, err := getIgnore(dockerIgnorePath) if err != nil { return "", nil, fmt.Errorf("error reading ignore file: %w", err) @@ -806,3 +809,19 @@ func defaultPooledTransport() *http.Transport { } return transport } + +func mapDockerignore(dockerfile string) string { + // Docker maps `Dockerfile` -> `.dockerignore` + // Nonstandard dockerfile names map to a file with a `.dockerignore` extension + // e.g. `Mockerfile` -> `Mockerfile.dockerignore` + // Note that we do not verify the existence of a .dockerignore file; we only map the name that it would have. + + ignore := ".dockerignore" + + // Add extension for nonstandardly named Dockerfiles + if dockerfile != defaultDockerfile { + ignore = dockerfile + ignore + } + // Return the default dockerignore name. + return ignore +} diff --git a/provider/image_test.go b/provider/image_test.go index 402f4fc9..5fe98830 100644 --- a/provider/image_test.go +++ b/provider/image_test.go @@ -497,3 +497,20 @@ func TestConfigureDockerClient(t *testing.T) { }) } + +func TestMapDockerignore(t *testing.T) { + + t.Run("Returns default .dockerignore", func(t *testing.T) { + expected := ".dockerignore" + input := defaultDockerfile + actual := mapDockerignore(input) + assert.Equal(t, expected, actual) + }) + t.Run("Returns .dockerignore extension for nonstandard dockerfile names", func(t *testing.T) { + expected := "special.dockerfile.dockerignore" + input := "special.dockerfile" + actual := mapDockerignore(input) + assert.Equal(t, expected, actual) + }) + +} diff --git a/provider/provider.go b/provider/provider.go index fcf3c8a9..4ccf3f22 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -523,7 +523,7 @@ func (accumulator *contextHashAccumulator) hexSumContext() string { func hashContext(dockerContextPath string, dockerfile string) (string, error) { // exclude all files listed in dockerignore - dockerIgnorePath := filepath.Join(dockerContextPath, ".dockerignore") + dockerIgnorePath := filepath.Join(dockerContextPath, mapDockerignore(filepath.Base(dockerfile))) ignorePatterns, err := getIgnore(dockerIgnorePath) if err != nil { return "", err