Files
buildkit/frontend/dockerfile/exclude_patterns_test.go
Jonathan A. Sternberg 5f01809fec dockerfile: promote --exclude flag from labs
Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-09-22 12:41:19 -05:00

279 lines
9.8 KiB
Go

package dockerfile
import (
"io/fs"
"net/http"
"net/http/httptest"
"os"
"path"
"path/filepath"
"testing"
"github.com/containerd/continuity/fs/fstest"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/frontend/dockerui"
"github.com/moby/buildkit/util/testutil/integration"
"github.com/stretchr/testify/require"
"github.com/tonistiigi/fsutil"
)
var excludedFilesTests = integration.TestFuncs(
testExcludedFilesOnCopy,
)
func init() {
allTests = append(allTests, excludedFilesTests...)
}
// See #4439
func testExcludedFilesOnCopy(t *testing.T, sb integration.Sandbox) {
integration.SkipOnPlatform(t, "windows")
ctx := sb.Context()
c, err := client.New(ctx, sb.Address())
require.NoError(t, err)
defer c.Close()
srcDir := integration.Tmpdir(
t,
fstest.CreateDir("dir1", fs.ModePerm),
fstest.CreateDir("dir2", fs.ModePerm),
fstest.CreateDir("dir3", fs.ModePerm),
fstest.CreateDir("dir4", fs.ModePerm),
fstest.CreateFile("dir1/file-101.png", []byte(`2`), 0600),
fstest.CreateFile("dir1/file-102.txt", []byte(`3`), 0600),
fstest.CreateFile("dir1/file-103.jpeg", []byte(`4`), 0600),
fstest.CreateFile("dir2/file-201.pdf", []byte(`6`), 0600),
fstest.CreateFile("dir2/file-202.jpeg", []byte(`7`), 0600),
fstest.CreateFile("dir2/file-203.png", []byte(`8`), 0600),
fstest.CreateFile("dir3/file-301.mp3", []byte(`9`), 0600),
fstest.CreateFile("dir3/file-302.mpeg", []byte(`10`), 0600),
fstest.CreateFile("dir4/file-401.txt", []byte(`11`), 0600),
fstest.Symlink("file-401.txt", "dir4/file-402.txt"),
fstest.Symlink("file-404.txt", "dir4/file-403.txt"),
)
runTest := func(dockerfile []byte, localMounts map[string]fsutil.FS) {
f := getFrontend(t, sb)
destDir := integration.Tmpdir(t)
dockerDir := integration.Tmpdir(
t,
fstest.CreateFile(dockerui.DefaultDockerfileName, dockerfile, 0600),
)
if localMounts == nil {
localMounts = map[string]fsutil.FS{}
}
localMounts[dockerui.DefaultLocalNameDockerfile] = dockerDir
_, err = f.Solve(ctx, c, client.SolveOpt{
LocalMounts: localMounts,
Exports: []client.ExportEntry{
{
Type: client.ExporterLocal,
OutputDir: destDir.Name,
},
},
}, nil)
require.NoError(t, err)
testCases := []struct {
filename string
excluded bool
expectedContent string
}{
// Files copied with COPY command
{filename: "builder1/1/dir1/file-101.png", excluded: true},
{filename: "builder1/1/dir1/file-102.txt", excluded: false, expectedContent: `3`},
{filename: "builder1/1/dir1/file-103.jpeg", excluded: true},
{filename: "builder1/1/dir2/file-201.pdf", excluded: false, expectedContent: `6`},
{filename: "builder1/1/dir2/file-202.jpeg", excluded: true},
{filename: "builder1/1/dir2/file-203.png", excluded: true},
{filename: "builder1/1/dir3/file-301.mp3", excluded: false, expectedContent: `9`},
{filename: "builder1/1/dir3/file-302.mpeg", excluded: false, expectedContent: `10`},
{filename: "builder1/1/dir4/file-401.txt", excluded: false, expectedContent: `11`},
{filename: "builder1/1/dir4/file-402.txt", excluded: false, expectedContent: `file-401.txt`},
{filename: "builder1/1/dir4/file-403.txt", excluded: false, expectedContent: `file-404.txt`},
// files copied with COPY command
{filename: "builder2/2/dir1/file-101.png", excluded: false, expectedContent: `2`},
{filename: "builder2/2/dir1/file-102.txt", excluded: true},
{filename: "builder2/2/dir1/file-103.jpeg", excluded: false, expectedContent: `4`},
{filename: "builder2/2/dir2/file-201.pdf", excluded: true},
{filename: "builder2/2/dir2/file-202.jpeg", excluded: false, expectedContent: `7`},
{filename: "builder2/2/dir2/file-203.png", excluded: false, expectedContent: `8`},
{filename: "builder2/2/dir3/file-301.mp3", excluded: false, expectedContent: `9`},
{filename: "builder2/2/dir3/file-302.mpeg", excluded: false, expectedContent: `10`},
{filename: "builder2/2/dir4/file-401.txt", excluded: true},
{filename: "builder2/2/dir4/file-402.txt", excluded: true},
{filename: "builder2/2/dir4/file-403.txt", excluded: true},
// Files copied with ADD command
{filename: "builder3/3/file-301.mp3", excluded: true},
{filename: "builder3/3/file-302.mpeg", excluded: false, expectedContent: `10`},
// Files copied with COPY wildcard
{filename: "builder4/4/file-101.png", excluded: true},
{filename: "builder4/4/file-102.txt", excluded: true},
{filename: "builder4/4/file-103.jpeg", excluded: true},
{filename: "builder4/4/file-201.pdf", excluded: true},
{filename: "builder4/4/file-202.jpeg", excluded: true},
{filename: "builder4/4/file-203.png", excluded: true},
{filename: "builder4/4/file-301.mp3", excluded: true},
{filename: "builder4/4/file-301.mp3", excluded: true},
{filename: "builder4/4/file-302.mpeg", excluded: false, expectedContent: `10`},
{filename: "builder4/4/dir4/file-401.txt", excluded: true},
{filename: "builder4/4/dir4/file-402.txt", excluded: true},
{filename: "builder4/4/dir4/file-403.txt", excluded: true},
// Files copied with ADD wildcard
{filename: "builder5/5/file-101.png", excluded: true},
{filename: "builder5/5/file-102.txt", excluded: false, expectedContent: `3`},
{filename: "builder5/5/file-103.jpeg", excluded: true},
{filename: "builder5/5/file-201.pdf", excluded: true},
{filename: "builder5/5/file-202.jpeg", excluded: true},
{filename: "builder5/5/file-203.png", excluded: true},
{filename: "builder5/5/file-301.mp3", excluded: true},
{filename: "builder5/5/file-301.mp3", excluded: true},
{filename: "builder5/5/file-302.mpeg", excluded: true},
{filename: "builder5/5/file-401.txt", excluded: false, expectedContent: `11`},
{filename: "builder5/5/file-402.txt", excluded: false, expectedContent: `file-401.txt`},
{filename: "builder5/5/file-403.txt", excluded: false, expectedContent: `file-404.txt`},
}
for _, tc := range testCases {
fpath := path.Join(destDir.Name, tc.filename)
var dt []byte
st, err := os.Lstat(fpath)
if tc.excluded {
require.Errorf(t, err, "File %s should not exist: %v", tc.filename, err)
continue
}
require.NoErrorf(t, err, "File %s should exist", tc.filename)
if st.Mode()&os.ModeSymlink != 0 {
target, err := os.Readlink(fpath)
require.NoErrorf(t, err, "Readlink %s should not error", tc.filename)
dt = []byte(target)
} else {
dt, err = os.ReadFile(fpath)
require.NoErrorf(t, err, "File %s should exist", tc.filename)
}
require.Equalf(t, tc.expectedContent, string(dt), "File %s does not have matched content", tc.filename)
}
// As all files are excluded, the directory must be empty
items, err := os.ReadDir(path.Join(destDir.Name, `builder6/6`))
require.NoError(t, err)
require.Empty(t, items)
}
// First, test with reading from a plain directory
runTest([]byte(`
# ignore all the image files
FROM scratch as builder1
COPY --exclude=*/*.png --exclude=*/*.jpeg . /1
# ignore all document files
FROM scratch as builder2
COPY --exclude=*/*.pdf --exclude=*/*.txt . /2
# ignore all mp3 files
FROM scratch as builder3
ADD --exclude=*.mp3 dir3/ /3/
# Ignore everything, keeping only .mpeg files, using src as a wildcard.
# it's a fake example, just to be sure that src as wildcards work as expected.
FROM scratch as builder4
COPY --exclude=*.mp3 --exclude=*.jpeg --exclude=*.png --exclude=*.pdf --exclude=*.txt dir* /4/
# Ignore everything, keeping only txt files.
# it's a fake example, just to be sure that src as wildcards work as expected.
FROM scratch as builder5
ADD --exclude=*.mp* --exclude=*.jpeg --exclude=*.png --exclude=*.pdf dir* /5
# Exclude all files. No idea how it could be useful, but it should not be forbidden.
FROM scratch as builder6
ADD --exclude=* dir* /6
FROM scratch
COPY --from=builder1 / /builder1
COPY --from=builder2 / /builder2
COPY --from=builder3 / /builder3
COPY --from=builder4 / /builder4
COPY --from=builder5 / /builder5
COPY --from=builder6 / /builder6
`), map[string]fsutil.FS{
dockerui.DefaultLocalNameContext: srcDir,
})
// Load context from a git repository
{
gitCommands := []string{
"git init",
"git config --local user.email test",
"git config --local user.name test",
"git add *",
"git commit -m 0.1",
"git tag 0.1",
"git update-server-info",
}
err = runShell(srcDir.Name, gitCommands...)
require.NoError(t, err)
server := httptest.NewServer(http.FileServer(http.Dir(filepath.Clean(srcDir.Name))))
defer server.Close()
serverURL := server.URL
// In this example, all the ADD commands were replaced by COPY, as ADD does not support
// the `--from=` flag, and we want to keep the assertions intact.
runTest([]byte(`
FROM scratch AS base
ARG REPO="`+serverURL+`/.git"
ARG TAG="0.1"
ADD --keep-git-dir=true ${REPO}#${TAG} /
# ignore all the image files
FROM scratch as builder1
COPY --from=base --exclude=*/*.png --exclude=*/*.jpeg . /1
# ignore all document files
FROM scratch as builder2
COPY --from=base --exclude=*/*.pdf --exclude=*/*.txt . /2
# ignore all mp3 files
FROM scratch as builder3
COPY --from=base --exclude=*.mp3 dir3/ /3/
# Ignore everything, keeping only .mpeg files, using src as a wildcard.
# it's a fake example, just to be sure that src as wildcards work as expected.
FROM scratch as builder4
COPY --from=base --exclude=*.mp3 --exclude=*.jpeg --exclude=*.png --exclude=*.pdf --exclude=*.txt dir* /4/
# Ignore everything, keeping only txt files.
# it's a fake example, just to be sure that src as wildcards work as expected.
FROM scratch as builder5
COPY --from=base --exclude=*.mp* --exclude=*.jpeg --exclude=*.png --exclude=*.pdf dir* /5
# Exclude all files. No idea how it could be useful, but it should not be forbidden.
FROM scratch as builder6
COPY --from=base --exclude=* dir* /6
FROM scratch
COPY --from=builder1 / /builder1
COPY --from=builder2 / /builder2
COPY --from=builder3 / /builder3
COPY --from=builder4 / /builder4
COPY --from=builder5 / /builder5
COPY --from=builder6 / /builder6
`), nil)
}
}