This PR was automatically generated by the update-native-provider-workflows workflow in the pulumi/ci-mgmt repo, from commit ea6479fcbca4b3b86182ffdd8731b26746795508. --------- Co-authored-by: Eron Wright <eronwright@gmail.com> Co-authored-by: Eron Wright <eron@pulumi.com>
425 lines
11 KiB
Go
425 lines
11 KiB
Go
// Copyright 2024, Pulumi Corporation.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package internal
|
|
|
|
import (
|
|
"bufio"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"testing"
|
|
|
|
"github.com/spf13/afero"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var _dockerfile = "Dockerfile"
|
|
|
|
func TestValidateContext(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
c *BuildContext
|
|
givenD Dockerfile
|
|
preview bool
|
|
|
|
wantD *Dockerfile
|
|
wantC *Context
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "relative",
|
|
c: &BuildContext{Context: Context{
|
|
Location: "../internal/../internal/testdata/noop",
|
|
}},
|
|
wantD: &Dockerfile{
|
|
Location: "../internal/testdata/noop/Dockerfile",
|
|
},
|
|
},
|
|
{
|
|
name: "missing directory",
|
|
c: &BuildContext{Context: Context{
|
|
Location: "/does/not/exist/",
|
|
}},
|
|
wantErr: "not a valid directory",
|
|
},
|
|
{
|
|
name: "missing default Dockerfile",
|
|
c: &BuildContext{Context: Context{
|
|
Location: "testdata",
|
|
}},
|
|
wantD: &Dockerfile{Location: "testdata/Dockerfile"},
|
|
},
|
|
{
|
|
name: "with explicit Dockerfile",
|
|
c: &BuildContext{Context: Context{
|
|
Location: "testdata",
|
|
}},
|
|
givenD: Dockerfile{
|
|
Location: "testdata/Dockerfile.invalid",
|
|
},
|
|
},
|
|
{
|
|
name: "default location",
|
|
c: &BuildContext{Context: Context{}},
|
|
wantD: &Dockerfile{Location: "Dockerfile"},
|
|
},
|
|
{
|
|
name: "remote context doesn't default to local Dockerfile",
|
|
c: &BuildContext{Context: Context{
|
|
Location: "https://raw.githubusercontent.com/pulumi/pulumi-docker/api-types/provider/testdata/Dockerfile",
|
|
}},
|
|
wantD: &Dockerfile{},
|
|
},
|
|
{
|
|
name: "preview",
|
|
c: &BuildContext{Context: Context{}},
|
|
preview: true,
|
|
},
|
|
{
|
|
name: "missing context defaults to current directory",
|
|
c: nil,
|
|
wantC: &Context{Location: "."},
|
|
wantD: &Dockerfile{Location: "Dockerfile"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
d, c, err := tt.c.validate(tt.preview, &tt.givenD)
|
|
|
|
if tt.wantErr == "" {
|
|
assert.NoError(t, err)
|
|
} else {
|
|
assert.ErrorContains(t, err, tt.wantErr)
|
|
}
|
|
|
|
if tt.wantD != nil {
|
|
assert.Equal(t, tt.wantD.Location, d.Location)
|
|
assert.Equal(t, tt.wantD.Inline, d.Inline)
|
|
}
|
|
if tt.wantC != nil {
|
|
assert.Equal(t, tt.wantC.Location, c.Location)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHashIgnoresFile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
step1Dir := "./testdata/ignores/basedir"
|
|
baseResult, err := hashBuildContext(step1Dir, filepath.Join(step1Dir, _dockerfile), nil)
|
|
require.NoError(t, err)
|
|
|
|
step2Dir := "./testdata/ignores/basedir-with-ignored-files"
|
|
result, err := hashBuildContext(step2Dir, filepath.Join(step2Dir, _dockerfile), nil)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, result, baseResult)
|
|
}
|
|
|
|
// Tests that we handle .dockerignore exclusions such as "!foo/*/bar".
|
|
//
|
|
// See:
|
|
// - https://github.com/moby/moby/issues/30018
|
|
// - https://github.com/moby/moby/issues/45608
|
|
//
|
|
// Buildkit handles these correctly (according to spec), Docker's classic builder does not.
|
|
func TestHashIgnoresWildcards(t *testing.T) {
|
|
t.Parallel()
|
|
baselineDir := "testdata/ignores-wildcard/basedir"
|
|
baselineResult, err := hashBuildContext(
|
|
baselineDir,
|
|
filepath.Join(baselineDir, _dockerfile),
|
|
nil,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
modIgnoredDir := "testdata/ignores-wildcard/basedir-modified-ignored-file"
|
|
modIgnoredResult, err := hashBuildContext(
|
|
modIgnoredDir,
|
|
filepath.Join(modIgnoredDir, _dockerfile),
|
|
nil,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
modIncludedDir := "testdata/ignores-wildcard/basedir-modified-included-file"
|
|
modIncludedResult, err := hashBuildContext(
|
|
modIncludedDir,
|
|
filepath.Join(modIncludedDir, _dockerfile),
|
|
nil,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(
|
|
t,
|
|
baselineResult,
|
|
modIgnoredResult,
|
|
"hash should not change when modifying ignored files",
|
|
)
|
|
assert.NotEqual(t, baselineResult, modIncludedResult,
|
|
"hash should change when modifying included (via wildcard ignore exclusion) files")
|
|
}
|
|
|
|
func BenchmarkHashBuildContext(b *testing.B) {
|
|
dir := "testdata/ignores-wildcard/basedir-modified-ignored-file"
|
|
for n := 0; n < b.N; n++ {
|
|
_, err := hashBuildContext(dir, filepath.Join(dir, _dockerfile), nil)
|
|
require.NoError(b, err)
|
|
|
|
}
|
|
}
|
|
|
|
// Tests that we handle .dockerignore exclusions such as "!foo/*/bar", as above, when using a
|
|
// relative context path.
|
|
//
|
|
//nolint:paralleltest // Incompatible with os.Chdir.
|
|
func TestHashIgnoresWildcardsRelative(t *testing.T) {
|
|
err := os.Chdir("testdata")
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
err = os.Chdir("..")
|
|
require.NoError(t, err)
|
|
}()
|
|
|
|
baselineDir := "../testdata/ignores-wildcard/basedir"
|
|
baselineResult, err := hashBuildContext(
|
|
baselineDir,
|
|
filepath.Join(baselineDir, _dockerfile),
|
|
nil,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
modIgnoredDir := "../testdata/ignores-wildcard/basedir-modified-ignored-file"
|
|
modIgnoredResult, err := hashBuildContext(
|
|
modIgnoredDir,
|
|
filepath.Join(modIgnoredDir, _dockerfile),
|
|
nil,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
modIncludedDir := "../testdata/ignores-wildcard/basedir-modified-included-file"
|
|
modIncludedResult, err := hashBuildContext(
|
|
modIncludedDir,
|
|
filepath.Join(modIncludedDir, _dockerfile),
|
|
nil,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(
|
|
t,
|
|
baselineResult,
|
|
modIgnoredResult,
|
|
"hash should not change when modifying ignored files",
|
|
)
|
|
assert.NotEqual(t, baselineResult, modIncludedResult,
|
|
"hash should change when modifying included (via wildcard ignore exclusion) files")
|
|
}
|
|
|
|
func TestHashIgnoresDockerfileOutsideDirMove(t *testing.T) {
|
|
t.Parallel()
|
|
appDir := "./testdata/dockerfile-location-irrelevant/app"
|
|
baseResult, err := hashBuildContext(
|
|
appDir,
|
|
"./testdata/dockerfile-location-irrelevant/step1.Dockerfile",
|
|
nil,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
result, err := hashBuildContext(
|
|
appDir,
|
|
"./testdata/dockerfile-location-irrelevant/step2.Dockerfile",
|
|
nil,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, result, baseResult)
|
|
}
|
|
|
|
func TestHashRenamingMatters(t *testing.T) {
|
|
t.Parallel()
|
|
step1Dir := "./testdata/filemode-matters/step1"
|
|
baseResult, err := hashBuildContext(step1Dir, filepath.Join(step1Dir, _dockerfile), nil)
|
|
require.NoError(t, err)
|
|
|
|
step2Dir := "./testdata/renaming-matters/step2"
|
|
result, err := hashBuildContext(step2Dir, filepath.Join(step2Dir, _dockerfile), nil)
|
|
require.NoError(t, err)
|
|
|
|
assert.NotEqual(t, result, baseResult)
|
|
}
|
|
|
|
func TestHashFilemodeMatters(t *testing.T) {
|
|
t.Parallel()
|
|
step1Dir := "./testdata/filemode-matters/step1"
|
|
baseResult, err := hashBuildContext(step1Dir, filepath.Join(step1Dir, _dockerfile), nil)
|
|
require.NoError(t, err)
|
|
|
|
step2Dir := "./testdata/filemode-matters/step2-chmod-x"
|
|
result, err := hashBuildContext(step2Dir, filepath.Join(step2Dir, _dockerfile), nil)
|
|
require.NoError(t, err)
|
|
|
|
assert.NotEqual(t, result, baseResult)
|
|
}
|
|
|
|
func TestHashDeepSymlinks(t *testing.T) {
|
|
t.Parallel()
|
|
dir := "./testdata/symlinks"
|
|
_, err := hashBuildContext(dir, filepath.Join(dir, "Dockerfile"), nil)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestIgnoreIrregularFiles(t *testing.T) {
|
|
t.Parallel()
|
|
dir := t.TempDir()
|
|
|
|
// Create a Dockerfile
|
|
dockerfile := filepath.Join(dir, "Dockerfile")
|
|
err := os.WriteFile(dockerfile, []byte{}, 0o600)
|
|
require.NoError(t, err)
|
|
|
|
// Create a pipe which should be ignored. (We will time out trying to read
|
|
// it if it's not.)
|
|
pipe := filepath.Join(dir, "pipe")
|
|
err = syscall.Mkfifo(pipe, 0o666)
|
|
require.NoError(t, err)
|
|
// Confirm it's irregular.
|
|
fi, err := os.Stat(pipe)
|
|
require.NoError(t, err)
|
|
assert.False(t, fi.Mode().IsRegular())
|
|
|
|
_, err = hashBuildContext(dir, dockerfile, nil)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestHashUnignoredDirs(t *testing.T) {
|
|
t.Parallel()
|
|
step1Dir := "./testdata/unignores/basedir"
|
|
baseResult, err := hashBuildContext(step1Dir, filepath.Join(step1Dir, _dockerfile), nil)
|
|
require.NoError(t, err)
|
|
|
|
step2Dir := "./testdata/unignores/basedir-with-unignored-files"
|
|
unignoreResult, err := hashBuildContext(step2Dir, filepath.Join(step2Dir, _dockerfile), nil)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, baseResult, unignoreResult)
|
|
}
|
|
|
|
func TestDockerIgnore(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
|
|
dockerfile string
|
|
context string
|
|
fs map[string]string
|
|
|
|
want []string
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "Dockerfile with root dockerignore",
|
|
dockerfile: "./foo/Dockerfile",
|
|
fs: map[string]string{
|
|
".dockerignore": "rootignore",
|
|
},
|
|
want: []string{"rootignore"},
|
|
},
|
|
{
|
|
name: "Dockerfile with root dockerignore and custom dockerignore",
|
|
dockerfile: "./foo/Dockerfile",
|
|
fs: map[string]string{
|
|
"foo/Dockerfile.dockerignore": "customignore",
|
|
".dockerignore": "rootignore",
|
|
},
|
|
want: []string{"customignore"},
|
|
},
|
|
{
|
|
name: "Dockerfile with root dockerignore and relative context",
|
|
dockerfile: "./foo/Dockerfile",
|
|
context: "../",
|
|
fs: map[string]string{
|
|
"../.dockerignore": "rootignore",
|
|
},
|
|
want: []string{"rootignore"},
|
|
},
|
|
{
|
|
name: "Dockerfile without root dockerignore",
|
|
dockerfile: "./foo/Dockerfile",
|
|
want: nil,
|
|
},
|
|
{
|
|
name: "Dockerfile with invalid root dockerignore",
|
|
dockerfile: "./foo/Dockerfile",
|
|
fs: map[string]string{
|
|
".dockerignore": strings.Repeat("*", bufio.MaxScanTokenSize),
|
|
},
|
|
wantErr: bufio.ErrTooLong,
|
|
},
|
|
{
|
|
name: "custom.Dockerfile without custom dockerignore and without root dockerignore",
|
|
dockerfile: "./foo/custom.Dockerfile",
|
|
want: nil,
|
|
},
|
|
{
|
|
name: "custom.Dockerfile with custom dockerignore and without root dockerignore",
|
|
dockerfile: "./foo/custom.Dockerfile",
|
|
fs: map[string]string{
|
|
"foo/custom.Dockerfile.dockerignore": "customignore",
|
|
},
|
|
want: []string{"customignore"},
|
|
},
|
|
{
|
|
name: "custom.Dockerfile with custom dockerignore and with root dockerignore",
|
|
dockerfile: "foo/custom.Dockerfile",
|
|
fs: map[string]string{
|
|
"foo/custom.Dockerfile.dockerignore": "customignore",
|
|
".dockerignore": "rootignore",
|
|
},
|
|
want: []string{"customignore"},
|
|
},
|
|
{
|
|
name: "custom.Dockerfile without custom dockerignore and with root dockerignore",
|
|
dockerfile: "foo/custom.Dockerfile",
|
|
fs: map[string]string{
|
|
".dockerignore": "rootignore",
|
|
},
|
|
want: []string{"rootignore"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
fs := afero.NewMemMapFs()
|
|
for fname, fdata := range tt.fs {
|
|
f, err := fs.Create(fname)
|
|
require.NoError(t, err)
|
|
_, err = f.WriteString(fdata)
|
|
require.NoError(t, err)
|
|
}
|
|
actual, err := getIgnorePatterns(fs, tt.dockerfile, tt.context)
|
|
|
|
assert.ErrorIs(t, err, tt.wantErr)
|
|
assert.Equal(t, tt.want, actual)
|
|
})
|
|
}
|
|
}
|