package dockerfile import ( "archive/tar" "bytes" "compress/gzip" "context" "encoding/json" "fmt" "io" "io/ioutil" "net/http" "net/http/httptest" "os" "os/exec" "path/filepath" "runtime" "sort" "strconv" "strings" "testing" "time" "github.com/containerd/containerd" "github.com/containerd/containerd/content" "github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/snapshots" "github.com/containerd/continuity/fs/fstest" "github.com/moby/buildkit/client" "github.com/moby/buildkit/client/llb" "github.com/moby/buildkit/frontend/dockerfile/builder" "github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb" gateway "github.com/moby/buildkit/frontend/gateway/client" "github.com/moby/buildkit/frontend/subrequests" "github.com/moby/buildkit/identity" "github.com/moby/buildkit/session" "github.com/moby/buildkit/session/upload/uploadprovider" "github.com/moby/buildkit/solver/errdefs" "github.com/moby/buildkit/util/contentutil" "github.com/moby/buildkit/util/testutil" "github.com/moby/buildkit/util/testutil/httpserver" "github.com/moby/buildkit/util/testutil/integration" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/stretchr/testify/require" ) func init() { if os.Getenv("TEST_DOCKERD") == "1" { integration.InitDockerdWorker() } else { integration.InitOCIWorker() integration.InitContainerdWorker() } } var allTests = []integration.Test{ testCmdShell, testGlobalArg, testDockerfileDirs, testDockerfileInvalidCommand, testDockerfileADDFromURL, testDockerfileAddArchive, testDockerfileScratchConfig, testExportedHistory, testExposeExpansion, testUser, testCacheReleased, testDockerignore, testDockerignoreInvalid, testDockerfileFromGit, testMultiStageImplicitFrom, testMultiStageCaseInsensitive, testLabels, testCacheImportExport, testReproducibleIDs, testImportExportReproducibleIDs, testNoCache, testDockerfileFromHTTP, testBuiltinArgs, testPullScratch, testSymlinkDestination, testHTTPDockerfile, testPlatformArgsImplicit, testPlatformArgsExplicit, testExportMultiPlatform, testQuotedMetaArgs, testIgnoreEntrypoint, testSymlinkedDockerfile, testDockerfileAddArchiveWildcard, testEmptyWildcard, testWorkdirCreatesDir, testDockerfileAddArchiveWildcard, testCopyChownExistingDir, testCopyWildcardCache, testDockerignoreOverride, testTarExporter, testDefaultEnvWithArgs, testEnvEmptyFormatting, testCacheMultiPlatformImportExport, testOnBuildCleared, testFrontendUseForwardedSolveResults, testFrontendInputs, testErrorsSourceMap, testMultiArgs, testFrontendSubrequests, testDockefileCheckHostname, testDefaultShellAndPath, testDockerfileLowercase, } var fileOpTests = []integration.Test{ testEmptyDestDir, testCopyChownCreateDest, testCopyThroughSymlinkContext, testCopyThroughSymlinkMultiStage, testCopySocket, testContextChangeDirToFile, testNoSnapshotLeak, testCopySymlinks, testCopyChown, testCopyChmod, testCopyOverrideFiles, testCopyVarSubstitution, testCopyWildcards, testCopyRelative, testTarContext, testTarContextExternalDockerfile, testWorkdirUser, testWorkdirExists, testWorkdirCopyIgnoreRelative, testCopyFollowAllSymlinks, testDockerfileAddChownExpand, } // Tests that depend on the `security.*` entitlements var securityTests = []integration.Test{} // Tests that depend on the `network.*` entitlements var networkTests = []integration.Test{} var opts []integration.TestOpt var securityOpts []integration.TestOpt type frontend interface { Solve(context.Context, *client.Client, client.SolveOpt, chan *client.SolveStatus) (*client.SolveResponse, error) DFCmdArgs(string, string) (string, string) RequiresBuildctl(t *testing.T) } func init() { frontends := map[string]interface{}{} opts = []integration.TestOpt{ integration.WithMirroredImages(integration.OfficialImages("busybox:latest")), integration.WithMirroredImages(map[string]string{ "docker/dockerfile-copy:v0.1.9": "docker.io/" + dockerfile2llb.DefaultCopyImage, }), integration.WithMatrix("frontend", frontends), } if os.Getenv("FRONTEND_BUILTIN_ONLY") == "1" { frontends["builtin"] = &builtinFrontend{} } else if os.Getenv("FRONTEND_CLIENT_ONLY") == "1" { frontends["client"] = &clientFrontend{} } else if gw := os.Getenv("FRONTEND_GATEWAY_ONLY"); gw != "" { name := "buildkit_test/" + identity.NewID() + ":latest" opts = append(opts, integration.WithMirroredImages(map[string]string{ name: gw, })) frontends["gateway"] = &gatewayFrontend{gw: name} } else { frontends["builtin"] = &builtinFrontend{} frontends["client"] = &clientFrontend{} } } func TestIntegration(t *testing.T) { integration.Run(t, allTests, opts...) integration.Run(t, fileOpTests, append(opts, integration.WithMatrix("fileop", map[string]interface{}{ "true": true, "false": false, }))...) integration.Run(t, securityTests, append(append(opts, securityOpts...), integration.WithMatrix("security.insecure", map[string]interface{}{ "granted": securityInsecureGranted, "denied": securityInsecureDenied, }))...) integration.Run(t, networkTests, append(opts, integration.WithMatrix("network.host", map[string]interface{}{ "granted": networkHostGranted, "denied": networkHostDenied, }))...) } func testDefaultEnvWithArgs(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM busybox AS build ARG my_arg ENV my_arg "my_arg=${my_arg:-def_val}" COPY myscript.sh myscript.sh RUN ./myscript.sh $my_arg FROM scratch COPY --from=build /out /out `) script := []byte(` #!/usr/bin/env sh echo -n $my_arg $1 > /out `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("myscript.sh", script, 0700), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) for _, x := range []struct { name string frontendAttrs map[string]string expected string }{ {"nil", nil, "my_arg=def_val my_arg=def_val"}, {"empty", map[string]string{"build-arg:my_arg": ""}, "my_arg=def_val my_arg=def_val"}, {"override", map[string]string{"build-arg:my_arg": "override"}, "my_arg=override my_arg=override"}, } { t.Run(x.name, func(t *testing.T) { _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: x.frontendAttrs, Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "out")) require.NoError(t, err) require.Equal(t, x.expected, string(dt)) }) } } func testEnvEmptyFormatting(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM busybox AS build ENV myenv foo%sbar RUN [ "$myenv" = 'foo%sbar' ] `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) } func testDockerignoreOverride(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM busybox COPY . . RUN [ -f foo ] && [ ! -f bar ] `) ignore := []byte(` bar `) dockerfile2 := []byte(` FROM busybox COPY . . RUN [ ! -f foo ] && [ -f bar ] `) ignore2 := []byte(` foo `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("Dockerfile.dockerignore", ignore, 0600), fstest.CreateFile("Dockerfile2", dockerfile2, 0600), fstest.CreateFile("Dockerfile2.dockerignore", ignore2, 0600), fstest.CreateFile("foo", []byte("contents0"), 0600), fstest.CreateFile("bar", []byte("contents0"), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "filename": "Dockerfile2", }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) } func testEmptyDestDir(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM busybox ENV empty="" COPY testfile $empty RUN [ "$(cat testfile)" == "contents0" ] `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("testfile", []byte("contents0"), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) } func testTarExporter(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM scratch AS stage-linux COPY foo forlinux FROM scratch AS stage-darwin COPY bar fordarwin FROM stage-$TARGETOS `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte("data"), 0600), fstest.CreateFile("bar", []byte("data2"), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() buf := &bytes.Buffer{} _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterTar, Output: fixedWriteCloser(&nopWriteCloser{buf}), }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) m, err := testutil.ReadTarToMap(buf.Bytes(), false) require.NoError(t, err) mi, ok := m["forlinux"] require.Equal(t, true, ok) require.Equal(t, "data", string(mi.Data)) // repeat multi-platform buf = &bytes.Buffer{} _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterTar, Output: fixedWriteCloser(&nopWriteCloser{buf}), }, }, FrontendAttrs: map[string]string{ "platform": "linux/amd64,darwin/amd64", }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) m, err = testutil.ReadTarToMap(buf.Bytes(), false) require.NoError(t, err) mi, ok = m["linux_amd64/forlinux"] require.Equal(t, true, ok) require.Equal(t, "data", string(mi.Data)) mi, ok = m["darwin_amd64/fordarwin"] require.Equal(t, true, ok) require.Equal(t, "data2", string(mi.Data)) } func testWorkdirCreatesDir(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM scratch WORKDIR /foo WORKDIR / `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) fi, err := os.Lstat(filepath.Join(destDir, "foo")) require.NoError(t, err) require.Equal(t, true, fi.IsDir()) } func testCacheReleased(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM busybox `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) checkAllReleasable(t, c, sb, true) } func testSymlinkedDockerfile(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM scratch ENV foo bar `) dir, err := tmpdir( fstest.CreateFile("Dockerfile.web", dockerfile, 0600), fstest.Symlink("Dockerfile.web", "Dockerfile"), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) } func testCopyChownExistingDir(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` # Set up files and directories with known ownership FROM busybox AS source RUN touch /file && chown 100:200 /file \ && mkdir -p /dir/subdir \ && touch /dir/subdir/nestedfile \ && chown 100:200 /dir \ && chown 101:201 /dir/subdir \ && chown 102:202 /dir/subdir/nestedfile FROM busybox AS test_base RUN mkdir -p /existingdir/existingsubdir \ && touch /existingdir/existingfile \ && chown 500:600 /existingdir \ && chown 501:601 /existingdir/existingsubdir \ && chown 501:601 /existingdir/existingfile # Copy files from the source stage FROM test_base AS copy_from COPY --from=source /file . # Copy to a non-existing target directory creates the target directory (as root), then copies the _contents_ of the source directory into it COPY --from=source /dir /dir # Copying to an existing target directory will copy the _contents_ of the source directory into it COPY --from=source /dir/. /existingdir RUN e="100:200"; p="/file" ; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi \ && e="0:0"; p="/dir" ; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi \ && e="101:201"; p="/dir/subdir" ; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi \ && e="102:202"; p="/dir/subdir/nestedfile" ; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi \ # Existing files and directories ownership should not be modified && e="500:600"; p="/existingdir" ; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi \ && e="501:601"; p="/existingdir/existingsubdir" ; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi \ && e="501:601"; p="/existingdir/existingfile" ; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi \ # But new files and directories should maintain their ownership && e="101:201"; p="/existingdir/subdir" ; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi \ && e="102:202"; p="/existingdir/subdir/nestedfile"; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi # Copy files from the source stage and chown them. FROM test_base AS copy_from_chowned COPY --from=source --chown=300:400 /file . # Copy to a non-existing target directory creates the target directory (as root), then copies the _contents_ of the source directory into it COPY --from=source --chown=300:400 /dir /dir # Copying to an existing target directory copies the _contents_ of the source directory into it COPY --from=source --chown=300:400 /dir/. /existingdir RUN e="300:400"; p="/file" ; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi \ && e="300:400"; p="/dir" ; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi \ && e="300:400"; p="/dir/subdir" ; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi \ && e="300:400"; p="/dir/subdir/nestedfile" ; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi \ # Existing files and directories ownership should not be modified && e="500:600"; p="/existingdir" ; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi \ && e="501:601"; p="/existingdir/existingsubdir" ; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi \ && e="501:601"; p="/existingdir/existingfile" ; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi \ # But new files and directories should be chowned && e="300:400"; p="/existingdir/subdir" ; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi \ && e="300:400"; p="/existingdir/subdir/nestedfile"; a=` + "`" + `stat -c "%u:%g" "$p"` + "`" + `; if [ "$a" != "$e" ]; then echo "incorrect ownership on $p. expected $e, got $a"; exit 1; fi `) dir, err := tmpdir( fstest.CreateFile("Dockerfile.web", dockerfile, 0600), fstest.Symlink("Dockerfile.web", "Dockerfile"), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "target": "copy_from", }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) } func testCopyWildcardCache(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM busybox AS base COPY foo* files/ RUN cat /dev/urandom | head -c 100 | sha256sum > unique COPY bar files/ FROM scratch COPY --from=base unique / `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo1", []byte("foo1-data"), 0600), fstest.CreateFile("foo2", []byte("foo2-data"), 0600), fstest.CreateFile("bar", []byte("bar-data"), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "unique")) require.NoError(t, err) err = ioutil.WriteFile(filepath.Join(dir, "bar"), []byte("bar-data-mod"), 0600) require.NoError(t, err) destDir, err = ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt2, err := ioutil.ReadFile(filepath.Join(destDir, "unique")) require.NoError(t, err) require.Equal(t, string(dt), string(dt2)) err = ioutil.WriteFile(filepath.Join(dir, "foo2"), []byte("foo2-data-mod"), 0600) require.NoError(t, err) destDir, err = ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt2, err = ioutil.ReadFile(filepath.Join(destDir, "unique")) require.NoError(t, err) require.NotEqual(t, string(dt), string(dt2)) } func testEmptyWildcard(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM scratch COPY foo nomatch* / `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte("contents0"), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "foo")) require.NoError(t, err) require.Equal(t, "contents0", string(dt)) } func testWorkdirUser(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM busybox RUN adduser -D user USER user WORKDIR /mydir RUN [ "$(stat -c "%U %G" /mydir)" == "user user" ] `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) } func testWorkdirCopyIgnoreRelative(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM scratch AS base WORKDIR /foo COPY Dockerfile / FROM scratch # relative path still loaded as absolute COPY --from=base Dockerfile . `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) } func testWorkdirExists(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM busybox RUN adduser -D user RUN mkdir /mydir && chown user:user /mydir WORKDIR /mydir RUN [ "$(stat -c "%U %G" /mydir)" == "user user" ] `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) } func testCopyChownCreateDest(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM busybox ARG group ENV owner user01 RUN adduser -D user RUN adduser -D user01 COPY --chown=user:user . /dest COPY --chown=${owner}:${group} . /dest01 RUN [ "$(stat -c "%U %G" /dest)" == "user user" ] RUN [ "$(stat -c "%U %G" /dest01)" == "user01 user" ] `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), "build-arg:group": "user", }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) } func testCopyThroughSymlinkContext(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM scratch COPY link/foo . `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.Symlink("sub", "link"), fstest.CreateDir("sub", 0700), fstest.CreateFile("sub/foo", []byte(`contents`), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "foo")) require.NoError(t, err) require.Equal(t, "contents", string(dt)) } func testCopyThroughSymlinkMultiStage(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM busybox AS build RUN mkdir -p /out/sub && ln -s /out/sub /sub && ln -s out/sub /sub2 && echo -n "data" > /sub/foo FROM scratch COPY --from=build /sub/foo . COPY --from=build /sub2/foo bar `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "foo")) require.NoError(t, err) require.Equal(t, "data", string(dt)) } func testCopySocket(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM scratch COPY . / `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateSocket("socket.sock", 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) fi, err := os.Lstat(filepath.Join(destDir, "socket.sock")) require.NoError(t, err) // make sure socket is converted to regular file. require.Equal(t, fi.Mode().IsRegular(), true) } func testIgnoreEntrypoint(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM busybox ENTRYPOINT ["/nosuchcmd"] RUN ["ls"] `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) } func testQuotedMetaArgs(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` ARG a1="box" ARG a2="$a1-foo" FROM busy$a1 AS build ARG a2 ARG a3="bar-$a2" RUN echo -n $a3 > /out FROM scratch COPY --from=build /out . `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "out")) require.NoError(t, err) require.Equal(t, "bar-box-foo", string(dt)) } func testMultiArgs(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` ARG a1="foo bar" a2=box ARG a3="$a2-foo" FROM busy$a2 AS build ARG a3 a4="123 456" a1 RUN echo -n "$a1:$a3:$a4" > /out FROM scratch COPY --from=build /out . `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "out")) require.NoError(t, err) require.Equal(t, "foo bar:box-foo:123 456", string(dt)) } func testDefaultShellAndPath(t *testing.T, sb integration.Sandbox) { skipDockerd(t, sb) f := getFrontend(t, sb) dockerfile := []byte(` FROM scratch ENTRYPOINT foo bar COPY Dockerfile . `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) out := filepath.Join(destDir, "out.tar") outW, err := os.Create(out) require.NoError(t, err) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, FrontendAttrs: map[string]string{ "platform": "windows/amd64,linux/amd64", }, Exports: []client.ExportEntry{ { Type: client.ExporterOCI, Output: fixedWriteCloser(outW), }, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "out.tar")) require.NoError(t, err) m, err := testutil.ReadTarToMap(dt, false) require.NoError(t, err) var idx ocispec.Index err = json.Unmarshal(m["index.json"].Data, &idx) require.NoError(t, err) mlistHex := idx.Manifests[0].Digest.Hex() idx = ocispec.Index{} err = json.Unmarshal(m["blobs/sha256/"+mlistHex].Data, &idx) require.NoError(t, err) require.Equal(t, 2, len(idx.Manifests)) for i, exp := range []struct { p string entrypoint []string env []string }{ {p: "windows/amd64", entrypoint: []string{"cmd", "/S", "/C", "foo bar"}, env: []string{"PATH=c:\\Windows\\System32;c:\\Windows"}}, {p: "linux/amd64", entrypoint: []string{"/bin/sh", "-c", "foo bar"}, env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}}, } { t.Run(exp.p, func(t *testing.T) { require.Equal(t, exp.p, platforms.Format(*idx.Manifests[i].Platform)) var mfst ocispec.Manifest err = json.Unmarshal(m["blobs/sha256/"+idx.Manifests[i].Digest.Hex()].Data, &mfst) require.NoError(t, err) require.Equal(t, 1, len(mfst.Layers)) var img ocispec.Image err = json.Unmarshal(m["blobs/sha256/"+mfst.Config.Digest.Hex()].Data, &img) require.NoError(t, err) require.Equal(t, exp.entrypoint, img.Config.Entrypoint) require.Equal(t, exp.env, img.Config.Env) }) } } func testExportMultiPlatform(t *testing.T, sb integration.Sandbox) { skipDockerd(t, sb) f := getFrontend(t, sb) dockerfile := []byte(` FROM scratch ARG TARGETARCH ARG TARGETPLATFORM LABEL target=$TARGETPLATFORM COPY arch-$TARGETARCH whoami `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("arch-arm", []byte(`i am arm`), 0600), fstest.CreateFile("arch-amd64", []byte(`i am amd64`), 0600), fstest.CreateFile("arch-s390x", []byte(`i am s390x`), 0600), fstest.CreateFile("arch-ppc64le", []byte(`i am ppc64le`), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, FrontendAttrs: map[string]string{ "platform": "windows/amd64,linux/arm,linux/s390x", }, Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "windows_amd64/whoami")) require.NoError(t, err) require.Equal(t, "i am amd64", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "linux_arm_v7/whoami")) require.NoError(t, err) require.Equal(t, "i am arm", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "linux_s390x/whoami")) require.NoError(t, err) require.Equal(t, "i am s390x", string(dt)) // repeat with oci exporter destDir, err = ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) out := filepath.Join(destDir, "out.tar") outW, err := os.Create(out) require.NoError(t, err) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, FrontendAttrs: map[string]string{ "platform": "windows/amd64,linux/arm/v6,linux/ppc64le", }, Exports: []client.ExportEntry{ { Type: client.ExporterOCI, Output: fixedWriteCloser(outW), }, }, }, nil) require.NoError(t, err) dt, err = ioutil.ReadFile(filepath.Join(destDir, "out.tar")) require.NoError(t, err) m, err := testutil.ReadTarToMap(dt, false) require.NoError(t, err) var idx ocispec.Index err = json.Unmarshal(m["index.json"].Data, &idx) require.NoError(t, err) mlistHex := idx.Manifests[0].Digest.Hex() idx = ocispec.Index{} err = json.Unmarshal(m["blobs/sha256/"+mlistHex].Data, &idx) require.NoError(t, err) require.Equal(t, 3, len(idx.Manifests)) for i, exp := range []struct { p string os string arch string dt string }{ {p: "windows/amd64", os: "windows", arch: "amd64", dt: "i am amd64"}, {p: "linux/arm/v6", os: "linux", arch: "arm", dt: "i am arm"}, {p: "linux/ppc64le", os: "linux", arch: "ppc64le", dt: "i am ppc64le"}, } { t.Run(exp.p, func(t *testing.T) { require.Equal(t, exp.p, platforms.Format(*idx.Manifests[i].Platform)) var mfst ocispec.Manifest err = json.Unmarshal(m["blobs/sha256/"+idx.Manifests[i].Digest.Hex()].Data, &mfst) require.NoError(t, err) require.Equal(t, 1, len(mfst.Layers)) m2, err := testutil.ReadTarToMap(m["blobs/sha256/"+mfst.Layers[0].Digest.Hex()].Data, true) require.NoError(t, err) require.Equal(t, exp.dt, string(m2["whoami"].Data)) var img ocispec.Image err = json.Unmarshal(m["blobs/sha256/"+mfst.Config.Digest.Hex()].Data, &img) require.NoError(t, err) require.Equal(t, exp.os, img.OS) require.Equal(t, exp.arch, img.Architecture) v, ok := img.Config.Labels["target"] require.True(t, ok) require.Equal(t, exp.p, v) }) } } // tonistiigi/fsutil#46 func testContextChangeDirToFile(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM scratch COPY foo / `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateDir("foo", 0700), fstest.CreateFile("foo/bar", []byte(`contents`), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dir, err = tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte(`contents2`), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "foo")) require.NoError(t, err) require.Equal(t, "contents2", string(dt)) } func testNoSnapshotLeak(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM scratch COPY foo / `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte(`contents`), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) du, err := c.DiskUsage(context.TODO()) require.NoError(t, err) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) du2, err := c.DiskUsage(context.TODO()) require.NoError(t, err) require.Equal(t, len(du), len(du2)) } // #1197 func testCopyFollowAllSymlinks(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM scratch COPY foo / COPY foo/sub bar `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("bar", []byte(`bar-contents`), 0600), fstest.CreateDir("foo", 0700), fstest.Symlink("../bar", "foo/sub"), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) } func testCopySymlinks(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM scratch COPY foo / COPY sub/l* alllinks/ `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("bar", []byte(`bar-contents`), 0600), fstest.Symlink("bar", "foo"), fstest.CreateDir("sub", 0700), fstest.CreateFile("sub/lfile", []byte(`lfile-contents`), 0600), fstest.Symlink("subfile", "sub/l0"), fstest.CreateFile("sub/subfile", []byte(`subfile-contents`), 0600), fstest.Symlink("second", "sub/l1"), fstest.Symlink("baz", "sub/second"), fstest.CreateFile("sub/baz", []byte(`baz-contents`), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "foo")) require.NoError(t, err) require.Equal(t, "bar-contents", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "alllinks/l0")) require.NoError(t, err) require.Equal(t, "subfile-contents", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "alllinks/lfile")) require.NoError(t, err) require.Equal(t, "lfile-contents", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "alllinks/l1")) require.NoError(t, err) require.Equal(t, "baz-contents", string(dt)) } func testHTTPDockerfile(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM busybox RUN echo -n "foo-contents" > /foo FROM scratch COPY --from=0 /foo /foo `) srcDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(srcDir) err = ioutil.WriteFile(filepath.Join(srcDir, "Dockerfile"), dockerfile, 0600) require.NoError(t, err) resp := httpserver.Response{ Etag: identity.NewID(), Content: dockerfile, } server := httpserver.NewTestServer(map[string]httpserver.Response{ "/df": resp, }) defer server.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "context": server.URL + "/df", "filename": "mydockerfile", // this is bogus, any name should work }, Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "foo")) require.NoError(t, err) require.Equal(t, "foo-contents", string(dt)) } func testCmdShell(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) cdAddress := sb.ContainerdAddress() if cdAddress == "" { t.Skip("test is only for containerd worker") } dockerfile := []byte(` FROM scratch CMD ["test"] `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() target := "docker.io/moby/cmdoverridetest:latest" _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterImage, Attrs: map[string]string{ "name": target, }, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dockerfile = []byte(` FROM docker.io/moby/cmdoverridetest:latest SHELL ["ls"] ENTRYPOINT my entrypoint `) dir, err = tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) target = "docker.io/moby/cmdoverridetest2:latest" _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterImage, Attrs: map[string]string{ "name": target, }, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) ctr, err := newContainerd(cdAddress) require.NoError(t, err) defer ctr.Close() ctx := namespaces.WithNamespace(context.Background(), "buildkit") img, err := ctr.ImageService().Get(ctx, target) require.NoError(t, err) desc, err := img.Config(ctx, ctr.ContentStore(), platforms.Default()) require.NoError(t, err) dt, err := content.ReadBlob(ctx, ctr.ContentStore(), desc) require.NoError(t, err) var ociimg ocispec.Image err = json.Unmarshal(dt, &ociimg) require.NoError(t, err) require.Equal(t, []string(nil), ociimg.Config.Cmd) require.Equal(t, []string{"ls", "my entrypoint"}, ociimg.Config.Entrypoint) } func testPullScratch(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) cdAddress := sb.ContainerdAddress() if cdAddress == "" { t.Skip("test is only for containerd worker") } dockerfile := []byte(` FROM scratch LABEL foo=bar `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() target := "docker.io/moby/testpullscratch:latest" _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterImage, Attrs: map[string]string{ "name": target, }, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dockerfile = []byte(` FROM docker.io/moby/testpullscratch:latest LABEL bar=baz COPY foo . `) dir, err = tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte("foo-contents"), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) target = "docker.io/moby/testpullscratch2:latest" _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterImage, Attrs: map[string]string{ "name": target, }, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) ctr, err := newContainerd(cdAddress) require.NoError(t, err) defer ctr.Close() ctx := namespaces.WithNamespace(context.Background(), "buildkit") img, err := ctr.ImageService().Get(ctx, target) require.NoError(t, err) desc, err := img.Config(ctx, ctr.ContentStore(), platforms.Default()) require.NoError(t, err) dt, err := content.ReadBlob(ctx, ctr.ContentStore(), desc) require.NoError(t, err) var ociimg ocispec.Image err = json.Unmarshal(dt, &ociimg) require.NoError(t, err) require.Equal(t, "layers", ociimg.RootFS.Type) require.Equal(t, 1, len(ociimg.RootFS.DiffIDs)) v, ok := ociimg.Config.Labels["foo"] require.True(t, ok) require.Equal(t, "bar", v) v, ok = ociimg.Config.Labels["bar"] require.True(t, ok) require.Equal(t, "baz", v) echo := llb.Image("busybox"). Run(llb.Shlex(`sh -c "echo -n foo0 > /empty/foo"`)). AddMount("/empty", llb.Image("docker.io/moby/testpullscratch:latest")) def, err := echo.Marshal(context.TODO()) require.NoError(t, err) destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = c.Solve(context.TODO(), def, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err = ioutil.ReadFile(filepath.Join(destDir, "foo")) require.NoError(t, err) require.Equal(t, "foo0", string(dt)) } func testGlobalArg(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` ARG tag=nosuchtag FROM busybox:${tag} `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "build-arg:tag": "latest", }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) } func testDockerfileDirs(t *testing.T, sb integration.Sandbox) { skipDockerd(t, sb) f := getFrontend(t, sb) f.RequiresBuildctl(t) dockerfile := []byte(` FROM busybox COPY foo /foo2 COPY foo / RUN echo -n bar > foo3 RUN test -f foo RUN cmp -s foo foo2 RUN cmp -s foo foo3 `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte("bar"), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) args, trace := f.DFCmdArgs(dir, dir) defer os.RemoveAll(trace) cmd := sb.Cmd(args) require.NoError(t, cmd.Run()) _, err = os.Stat(trace) require.NoError(t, err) // relative urls args, trace = f.DFCmdArgs(".", ".") defer os.RemoveAll(trace) cmd = sb.Cmd(args) cmd.Dir = dir require.NoError(t, cmd.Run()) _, err = os.Stat(trace) require.NoError(t, err) // different context and dockerfile directories dir1, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir1) dir2, err := tmpdir( fstest.CreateFile("foo", []byte("bar"), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir2) args, trace = f.DFCmdArgs(dir2, dir1) defer os.RemoveAll(trace) cmd = sb.Cmd(args) cmd.Dir = dir require.NoError(t, cmd.Run()) _, err = os.Stat(trace) require.NoError(t, err) // TODO: test trace file output, cache hits, logs etc. // TODO: output metadata about original dockerfile command in trace } func testDockerfileInvalidCommand(t *testing.T, sb integration.Sandbox) { skipDockerd(t, sb) f := getFrontend(t, sb) f.RequiresBuildctl(t) dockerfile := []byte(` FROM busybox RUN invalidcmd `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) args, trace := f.DFCmdArgs(dir, dir) defer os.RemoveAll(trace) cmd := sb.Cmd(args) stdout := new(bytes.Buffer) cmd.Stderr = stdout err = cmd.Run() require.Error(t, err) require.Contains(t, stdout.String(), "/bin/sh -c invalidcmd") require.Contains(t, stdout.String(), "executor failed running") } func testDockerfileADDFromURL(t *testing.T, sb integration.Sandbox) { skipDockerd(t, sb) f := getFrontend(t, sb) f.RequiresBuildctl(t) modTime := time.Now().Add(-24 * time.Hour) // avoid falso positive with current time resp := httpserver.Response{ Etag: identity.NewID(), Content: []byte("content1"), } resp2 := httpserver.Response{ Etag: identity.NewID(), LastModified: &modTime, Content: []byte("content2"), } server := httpserver.NewTestServer(map[string]httpserver.Response{ "/foo": resp, "/": resp2, }) defer server.Close() dockerfile := []byte(fmt.Sprintf(` FROM scratch ADD %s /dest/ `, server.URL+"/foo")) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) args, trace := f.DFCmdArgs(dir, dir) defer os.RemoveAll(trace) destDir, err := tmpdir() require.NoError(t, err) defer os.RemoveAll(destDir) cmd := sb.Cmd(args + fmt.Sprintf(" --exporter=local --exporter-opt output=%s", destDir)) err = cmd.Run() require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "dest/foo")) require.NoError(t, err) require.Equal(t, []byte("content1"), dt) // test the default properties dockerfile = []byte(fmt.Sprintf(` FROM scratch ADD %s /dest/ `, server.URL+"/")) dir, err = tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) args, trace = f.DFCmdArgs(dir, dir) defer os.RemoveAll(trace) destDir, err = tmpdir() require.NoError(t, err) defer os.RemoveAll(destDir) cmd = sb.Cmd(args + fmt.Sprintf(" --exporter=local --exporter-opt output=%s", destDir)) err = cmd.Run() require.NoError(t, err) destFile := filepath.Join(destDir, "dest/__unnamed__") dt, err = ioutil.ReadFile(destFile) require.NoError(t, err) require.Equal(t, []byte("content2"), dt) fi, err := os.Stat(destFile) require.NoError(t, err) require.Equal(t, modTime.Format(http.TimeFormat), fi.ModTime().Format(http.TimeFormat)) } func testDockerfileAddArchive(t *testing.T, sb integration.Sandbox) { skipDockerd(t, sb) f := getFrontend(t, sb) f.RequiresBuildctl(t) buf := bytes.NewBuffer(nil) tw := tar.NewWriter(buf) expectedContent := []byte("content0") err := tw.WriteHeader(&tar.Header{ Name: "foo", Typeflag: tar.TypeReg, Size: int64(len(expectedContent)), Mode: 0644, }) require.NoError(t, err) _, err = tw.Write(expectedContent) require.NoError(t, err) err = tw.Close() require.NoError(t, err) dockerfile := []byte(` FROM scratch ADD t.tar / `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("t.tar", buf.Bytes(), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) args, trace := f.DFCmdArgs(dir, dir) defer os.RemoveAll(trace) destDir, err := tmpdir() require.NoError(t, err) defer os.RemoveAll(destDir) cmd := sb.Cmd(args + fmt.Sprintf(" --exporter=local --exporter-opt output=%s", destDir)) require.NoError(t, cmd.Run()) dt, err := ioutil.ReadFile(filepath.Join(destDir, "foo")) require.NoError(t, err) require.Equal(t, expectedContent, dt) // add gzip tar buf2 := bytes.NewBuffer(nil) gz := gzip.NewWriter(buf2) _, err = gz.Write(buf.Bytes()) require.NoError(t, err) err = gz.Close() require.NoError(t, err) dockerfile = []byte(` FROM scratch ADD t.tar.gz / `) dir, err = tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("t.tar.gz", buf2.Bytes(), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) args, trace = f.DFCmdArgs(dir, dir) defer os.RemoveAll(trace) destDir, err = tmpdir() require.NoError(t, err) defer os.RemoveAll(destDir) cmd = sb.Cmd(args + fmt.Sprintf(" --exporter=local --exporter-opt output=%s", destDir)) require.NoError(t, cmd.Run()) dt, err = ioutil.ReadFile(filepath.Join(destDir, "foo")) require.NoError(t, err) require.Equal(t, expectedContent, dt) // COPY doesn't extract dockerfile = []byte(` FROM scratch COPY t.tar.gz / `) dir, err = tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("t.tar.gz", buf2.Bytes(), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) args, trace = f.DFCmdArgs(dir, dir) defer os.RemoveAll(trace) destDir, err = tmpdir() require.NoError(t, err) defer os.RemoveAll(destDir) cmd = sb.Cmd(args + fmt.Sprintf(" --exporter=local --exporter-opt output=%s", destDir)) require.NoError(t, cmd.Run()) dt, err = ioutil.ReadFile(filepath.Join(destDir, "t.tar.gz")) require.NoError(t, err) require.Equal(t, buf2.Bytes(), dt) // ADD from URL doesn't extract resp := httpserver.Response{ Etag: identity.NewID(), Content: buf2.Bytes(), } server := httpserver.NewTestServer(map[string]httpserver.Response{ "/t.tar.gz": resp, }) defer server.Close() dockerfile = []byte(fmt.Sprintf(` FROM scratch ADD %s / `, server.URL+"/t.tar.gz")) dir, err = tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) args, trace = f.DFCmdArgs(dir, dir) defer os.RemoveAll(trace) destDir, err = tmpdir() require.NoError(t, err) defer os.RemoveAll(destDir) cmd = sb.Cmd(args + fmt.Sprintf(" --exporter=local --exporter-opt output=%s", destDir)) require.NoError(t, cmd.Run()) dt, err = ioutil.ReadFile(filepath.Join(destDir, "t.tar.gz")) require.NoError(t, err) require.Equal(t, buf2.Bytes(), dt) // https://github.com/moby/buildkit/issues/386 dockerfile = []byte(fmt.Sprintf(` FROM scratch ADD %s /newname.tar.gz `, server.URL+"/t.tar.gz")) dir, err = tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) args, trace = f.DFCmdArgs(dir, dir) defer os.RemoveAll(trace) destDir, err = tmpdir() require.NoError(t, err) defer os.RemoveAll(destDir) cmd = sb.Cmd(args + fmt.Sprintf(" --exporter=local --exporter-opt output=%s", destDir)) require.NoError(t, cmd.Run()) dt, err = ioutil.ReadFile(filepath.Join(destDir, "newname.tar.gz")) require.NoError(t, err) require.Equal(t, buf2.Bytes(), dt) } func testDockerfileAddArchiveWildcard(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) buf := bytes.NewBuffer(nil) tw := tar.NewWriter(buf) expectedContent := []byte("content0") err := tw.WriteHeader(&tar.Header{ Name: "foo", Typeflag: tar.TypeReg, Size: int64(len(expectedContent)), Mode: 0644, }) require.NoError(t, err) _, err = tw.Write(expectedContent) require.NoError(t, err) err = tw.Close() require.NoError(t, err) buf2 := bytes.NewBuffer(nil) tw = tar.NewWriter(buf2) expectedContent = []byte("content1") err = tw.WriteHeader(&tar.Header{ Name: "bar", Typeflag: tar.TypeReg, Size: int64(len(expectedContent)), Mode: 0644, }) require.NoError(t, err) _, err = tw.Write(expectedContent) require.NoError(t, err) err = tw.Close() require.NoError(t, err) dockerfile := []byte(` FROM scratch ADD *.tar /dest `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("t.tar", buf.Bytes(), 0600), fstest.CreateFile("b.tar", buf2.Bytes(), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "dest/foo")) require.NoError(t, err) require.Equal(t, "content0", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "dest/bar")) require.NoError(t, err) require.Equal(t, "content1", string(dt)) } func testDockerfileAddChownExpand(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM busybox ARG group ENV owner 1000 ADD --chown=${owner}:${group} foo / RUN [ "$(stat -c "%u %G" /foo)" == "1000 nobody" ] `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte(`foo-contents`), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), "build-arg:group": "nobody", }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) } func testSymlinkDestination(t *testing.T, sb integration.Sandbox) { skipDockerd(t, sb) f := getFrontend(t, sb) f.RequiresBuildctl(t) buf := bytes.NewBuffer(nil) tw := tar.NewWriter(buf) expectedContent := []byte("content0") err := tw.WriteHeader(&tar.Header{ Name: "symlink", Typeflag: tar.TypeSymlink, Linkname: "../tmp/symlink-target", Mode: 0755, }) require.NoError(t, err) err = tw.Close() require.NoError(t, err) dockerfile := []byte(` FROM scratch ADD t.tar / COPY foo /symlink/ `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", expectedContent, 0600), fstest.CreateFile("t.tar", buf.Bytes(), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) args, trace := f.DFCmdArgs(dir, dir) defer os.RemoveAll(trace) destDir, err := tmpdir() require.NoError(t, err) defer os.RemoveAll(destDir) cmd := sb.Cmd(args + fmt.Sprintf(" --exporter=local --exporter-opt output=%s", destDir)) require.NoError(t, cmd.Run()) dt, err := ioutil.ReadFile(filepath.Join(destDir, "tmp/symlink-target/foo")) require.NoError(t, err) require.Equal(t, expectedContent, dt) } func testDockerfileScratchConfig(t *testing.T, sb integration.Sandbox) { cdAddress := sb.ContainerdAddress() if cdAddress == "" { t.Skip("test requires containerd worker") } f := getFrontend(t, sb) f.RequiresBuildctl(t) dockerfile := []byte(` FROM scratch ENV foo=bar `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) args, trace := f.DFCmdArgs(dir, dir) defer os.RemoveAll(trace) target := "example.com/moby/dockerfilescratch:test" cmd := sb.Cmd(args + " --exporter=image --exporter-opt=name=" + target) err = cmd.Run() require.NoError(t, err) client, err := newContainerd(cdAddress) require.NoError(t, err) defer client.Close() ctx := namespaces.WithNamespace(context.Background(), "buildkit") img, err := client.ImageService().Get(ctx, target) require.NoError(t, err) desc, err := img.Config(ctx, client.ContentStore(), platforms.Default()) require.NoError(t, err) dt, err := content.ReadBlob(ctx, client.ContentStore(), desc) require.NoError(t, err) var ociimg ocispec.Image err = json.Unmarshal(dt, &ociimg) require.NoError(t, err) require.NotEqual(t, "", ociimg.OS) require.NotEqual(t, "", ociimg.Architecture) require.NotEqual(t, "", ociimg.Config.WorkingDir) require.Equal(t, "layers", ociimg.RootFS.Type) require.Equal(t, 0, len(ociimg.RootFS.DiffIDs)) require.Equal(t, 1, len(ociimg.History)) require.Contains(t, ociimg.History[0].CreatedBy, "ENV foo=bar") require.Equal(t, true, ociimg.History[0].EmptyLayer) require.Contains(t, ociimg.Config.Env, "foo=bar") require.Condition(t, func() bool { for _, env := range ociimg.Config.Env { if strings.HasPrefix(env, "PATH=") { return true } } return false }) } func testExposeExpansion(t *testing.T, sb integration.Sandbox) { skipDockerd(t, sb) f := getFrontend(t, sb) dockerfile := []byte(` FROM scratch ARG PORTS="3000 4000/udp" EXPOSE $PORTS EXPOSE 5000 `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() target := "example.com/moby/dockerfileexpansion:test" _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterImage, Attrs: map[string]string{ "name": target, }, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) cdAddress := sb.ContainerdAddress() if cdAddress == "" { t.Skip("rest of test requires containerd worker") } client, err := newContainerd(cdAddress) require.NoError(t, err) defer client.Close() ctx := namespaces.WithNamespace(context.Background(), "buildkit") img, err := client.ImageService().Get(ctx, target) require.NoError(t, err) desc, err := img.Config(ctx, client.ContentStore(), platforms.Default()) require.NoError(t, err) dt, err := content.ReadBlob(ctx, client.ContentStore(), desc) require.NoError(t, err) var ociimg ocispec.Image err = json.Unmarshal(dt, &ociimg) require.NoError(t, err) require.Equal(t, 3, len(ociimg.Config.ExposedPorts)) var ports []string for p := range ociimg.Config.ExposedPorts { ports = append(ports, p) } sort.Strings(ports) require.Equal(t, "3000/tcp", ports[0]) require.Equal(t, "4000/udp", ports[1]) require.Equal(t, "5000/tcp", ports[2]) } func testDockerignore(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM scratch COPY . . `) dockerignore := []byte(` ba* Dockerfile !bay .dockerignore `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte(`foo-contents`), 0600), fstest.CreateFile("bar", []byte(`bar-contents`), 0600), fstest.CreateFile("baz", []byte(`baz-contents`), 0600), fstest.CreateFile("bay", []byte(`bay-contents`), 0600), fstest.CreateFile(".dockerignore", dockerignore, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "foo")) require.NoError(t, err) require.Equal(t, "foo-contents", string(dt)) _, err = os.Stat(filepath.Join(destDir, ".dockerignore")) require.Error(t, err) require.True(t, errors.Is(err, os.ErrNotExist)) _, err = os.Stat(filepath.Join(destDir, "Dockerfile")) require.Error(t, err) require.True(t, errors.Is(err, os.ErrNotExist)) _, err = os.Stat(filepath.Join(destDir, "bar")) require.Error(t, err) require.True(t, errors.Is(err, os.ErrNotExist)) _, err = os.Stat(filepath.Join(destDir, "baz")) require.Error(t, err) require.True(t, errors.Is(err, os.ErrNotExist)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "bay")) require.NoError(t, err) require.Equal(t, "bay-contents", string(dt)) } func testDockerignoreInvalid(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM scratch COPY . . `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile(".dockerignore", []byte("!\n"), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() c, err := client.New(ctx, sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(ctx, c, client.SolveOpt{ LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) // err is either the expected error due to invalid dockerignore or error from the timeout require.Error(t, err) select { case <-ctx.Done(): t.Fatal("timed out") default: } } // moby/moby#10858 func testDockerfileLowercase(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(`FROM scratch `) dir, err := tmpdir( fstest.CreateFile("dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) ctx := context.TODO() c, err := client.New(ctx, sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(ctx, c, client.SolveOpt{ LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) } func testExportedHistory(t *testing.T, sb integration.Sandbox) { skipDockerd(t, sb) f := getFrontend(t, sb) f.RequiresBuildctl(t) // using multi-stage to test that history is scoped to one stage dockerfile := []byte(` FROM busybox AS base ENV foo=bar COPY foo /foo2 FROM busybox LABEL lbl=val COPY --from=base foo2 foo3 WORKDIR / RUN echo bar > foo4 RUN ["ls"] `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte("contents0"), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) args, trace := f.DFCmdArgs(dir, dir) defer os.RemoveAll(trace) target := "example.com/moby/dockerfilescratch:test" cmd := sb.Cmd(args + " --exporter=image --exporter-opt=name=" + target) require.NoError(t, cmd.Run()) // TODO: expose this test to OCI worker cdAddress := sb.ContainerdAddress() if cdAddress == "" { t.Skip("rest of test requires containerd worker") } client, err := newContainerd(cdAddress) require.NoError(t, err) defer client.Close() ctx := namespaces.WithNamespace(context.Background(), "buildkit") img, err := client.ImageService().Get(ctx, target) require.NoError(t, err) desc, err := img.Config(ctx, client.ContentStore(), platforms.Default()) require.NoError(t, err) dt, err := content.ReadBlob(ctx, client.ContentStore(), desc) require.NoError(t, err) var ociimg ocispec.Image err = json.Unmarshal(dt, &ociimg) require.NoError(t, err) require.Equal(t, "layers", ociimg.RootFS.Type) // this depends on busybox. should be ok after freezing images require.Equal(t, 3, len(ociimg.RootFS.DiffIDs)) require.Equal(t, 7, len(ociimg.History)) require.Contains(t, ociimg.History[2].CreatedBy, "lbl=val") require.Equal(t, true, ociimg.History[2].EmptyLayer) require.NotNil(t, ociimg.History[2].Created) require.Contains(t, ociimg.History[3].CreatedBy, "COPY foo2 foo3") require.Equal(t, false, ociimg.History[3].EmptyLayer) require.NotNil(t, ociimg.History[3].Created) require.Contains(t, ociimg.History[4].CreatedBy, "WORKDIR /") require.Equal(t, true, ociimg.History[4].EmptyLayer) require.NotNil(t, ociimg.History[4].Created) require.Contains(t, ociimg.History[5].CreatedBy, "echo bar > foo4") require.Equal(t, false, ociimg.History[5].EmptyLayer) require.NotNil(t, ociimg.History[5].Created) require.Contains(t, ociimg.History[6].CreatedBy, "RUN ls") require.Equal(t, true, ociimg.History[6].EmptyLayer) require.NotNil(t, ociimg.History[6].Created) } func skipDockerd(t *testing.T, sb integration.Sandbox) { // TODO: remove me once dockerd supports the image and exporter. t.Helper() if os.Getenv("TEST_DOCKERD") == "1" { t.Skip("dockerd missing a required exporter, cache exporter, or entitlement") } } func testUser(t *testing.T, sb integration.Sandbox) { skipDockerd(t, sb) f := getFrontend(t, sb) dockerfile := []byte(` FROM busybox AS base RUN mkdir -m 0777 /out RUN id -un > /out/rootuser # Make sure our defaults work RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)" = '0:0/root:root' ] # TODO decide if "args.user = strconv.Itoa(syscall.Getuid())" is acceptable behavior for changeUser in sysvinit instead of "return nil" when "USER" isn't specified (so that we get the proper group list even if that is the empty list, even in the default case of not supplying an explicit USER to run as, which implies USER 0) USER root RUN [ "$(id -G):$(id -Gn)" = '0 10:root wheel' ] # Setup dockerio user and group RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd && \ echo 'dockerio:x:1001:' >> /etc/group # Make sure we can switch to our user and all the information is exactly as we expect it to be USER dockerio RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ] # Switch back to root and double check that worked exactly as we might expect it to USER root RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '0:0/root:root/0 10:root wheel' ] && \ # Add a "supplementary" group for our dockerio user echo 'supplementary:x:1002:dockerio' >> /etc/group # ... and then go verify that we get it like we expect USER dockerio RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001 1002:dockerio supplementary' ] USER 1001 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001 1002:dockerio supplementary' ] # super test the new "user:group" syntax USER dockerio:dockerio RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ] USER 1001:dockerio RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ] USER dockerio:1001 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ] USER 1001:1001 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1001/dockerio:dockerio/1001:dockerio' ] USER dockerio:supplementary RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1002/dockerio:supplementary/1002:supplementary' ] USER dockerio:1002 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1002/dockerio:supplementary/1002:supplementary' ] USER 1001:supplementary RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1002/dockerio:supplementary/1002:supplementary' ] USER 1001:1002 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1001:1002/dockerio:supplementary/1002:supplementary' ] # make sure unknown uid/gid still works properly USER 1042:1043 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1042:1043/1042:1043/1043:1043' ] USER daemon RUN id -un > /out/daemonuser FROM scratch COPY --from=base /out / USER nobody `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "rootuser")) require.NoError(t, err) require.Equal(t, "root\n", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "daemonuser")) require.NoError(t, err) require.Equal(t, "daemon\n", string(dt)) // test user in exported target := "example.com/moby/dockerfileuser:test" _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterImage, Attrs: map[string]string{ "name": target, }, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) cdAddress := sb.ContainerdAddress() if cdAddress == "" { t.Skip("rest of test requires containerd worker") } client, err := newContainerd(cdAddress) require.NoError(t, err) defer client.Close() ctx := namespaces.WithNamespace(context.Background(), "buildkit") img, err := client.ImageService().Get(ctx, target) require.NoError(t, err) desc, err := img.Config(ctx, client.ContentStore(), platforms.Default()) require.NoError(t, err) dt, err = content.ReadBlob(ctx, client.ContentStore(), desc) require.NoError(t, err) var ociimg ocispec.Image err = json.Unmarshal(dt, &ociimg) require.NoError(t, err) require.Equal(t, "nobody", ociimg.Config.User) } func testCopyChown(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM busybox AS base ENV owner 1000 RUN mkdir -m 0777 /out COPY --chown=daemon foo / COPY --chown=1000:nobody bar /baz ARG group COPY --chown=${owner}:${group} foo /foobis RUN stat -c "%U %G" /foo > /out/fooowner RUN stat -c "%u %G" /baz/sub > /out/subowner RUN stat -c "%u %G" /foobis > /out/foobisowner FROM scratch COPY --from=base /out / `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte(`foo-contents`), 0600), fstest.CreateDir("bar", 0700), fstest.CreateFile("bar/sub", nil, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), "build-arg:group": "nobody", }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "fooowner")) require.NoError(t, err) require.Equal(t, "daemon daemon\n", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "subowner")) require.NoError(t, err) require.Equal(t, "1000 nobody\n", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "foobisowner")) require.NoError(t, err) require.Equal(t, "1000 nobody\n", string(dt)) } func testCopyChmod(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM busybox AS base RUN mkdir -m 0777 /out COPY --chmod=0644 foo / COPY --chmod=777 bar /baz COPY --chmod=0 foo /foobis RUN stat -c "%04a" /foo > /out/fooperm RUN stat -c "%04a" /baz > /out/barperm RUN stat -c "%04a" /foobis > /out/foobisperm FROM scratch COPY --from=base /out / `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte(`foo-contents`), 0600), fstest.CreateFile("bar", []byte(`bar-contents`), 0700), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) if !isFileOp { require.Contains(t, err.Error(), "chmod is not supported") return } require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "fooperm")) require.NoError(t, err) require.Equal(t, "0644\n", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "barperm")) require.NoError(t, err) require.Equal(t, "0777\n", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "foobisperm")) require.NoError(t, err) require.Equal(t, "0000\n", string(dt)) } func testCopyOverrideFiles(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM scratch AS base COPY sub sub COPY sub sub COPY files/foo.go dest/foo.go COPY files/foo.go dest/foo.go COPY files dest `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateDir("sub", 0700), fstest.CreateDir("sub/dir1", 0700), fstest.CreateDir("sub/dir1/dir2", 0700), fstest.CreateFile("sub/dir1/dir2/foo", []byte(`foo-contents`), 0600), fstest.CreateDir("files", 0700), fstest.CreateFile("files/foo.go", []byte(`foo.go-contents`), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "sub/dir1/dir2/foo")) require.NoError(t, err) require.Equal(t, "foo-contents", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "dest/foo.go")) require.NoError(t, err) require.Equal(t, "foo.go-contents", string(dt)) } func testCopyVarSubstitution(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM scratch AS base ENV FOO bar COPY $FOO baz `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("bar", []byte(`bar-contents`), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "baz")) require.NoError(t, err) require.Equal(t, "bar-contents", string(dt)) } func testCopyWildcards(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM scratch AS base COPY *.go /gofiles/ COPY f*.go foo2.go COPY sub/* /subdest/ COPY sub/*/dir2/foo /subdest2/ COPY sub/*/dir2/foo /subdest3/bar COPY . all/ COPY sub/dir1/ subdest4 COPY sub/dir1/. subdest5 COPY sub/dir1 subdest6 `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo.go", []byte(`foo-contents`), 0600), fstest.CreateFile("bar.go", []byte(`bar-contents`), 0600), fstest.CreateDir("sub", 0700), fstest.CreateDir("sub/dir1", 0700), fstest.CreateDir("sub/dir1/dir2", 0700), fstest.CreateFile("sub/dir1/dir2/foo", []byte(`foo-contents`), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "gofiles/foo.go")) require.NoError(t, err) require.Equal(t, "foo-contents", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "gofiles/bar.go")) require.NoError(t, err) require.Equal(t, "bar-contents", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "foo2.go")) require.NoError(t, err) require.Equal(t, "foo-contents", string(dt)) if isFileOp { // non-fileop implementation is historically buggy dt, err = ioutil.ReadFile(filepath.Join(destDir, "subdest/dir2/foo")) require.NoError(t, err) require.Equal(t, "foo-contents", string(dt)) } dt, err = ioutil.ReadFile(filepath.Join(destDir, "subdest2/foo")) require.NoError(t, err) require.Equal(t, "foo-contents", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "subdest3/bar")) require.NoError(t, err) require.Equal(t, "foo-contents", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "all/foo.go")) require.NoError(t, err) require.Equal(t, "foo-contents", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "subdest4/dir2/foo")) require.NoError(t, err) require.Equal(t, "foo-contents", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "subdest5/dir2/foo")) require.NoError(t, err) require.Equal(t, "foo-contents", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "subdest6/dir2/foo")) require.NoError(t, err) require.Equal(t, "foo-contents", string(dt)) } func testCopyRelative(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM busybox WORKDIR /test1 WORKDIR test2 RUN sh -c "[ "$PWD" = '/test1/test2' ]" COPY foo ./ RUN sh -c "[ $(cat /test1/test2/foo) = 'hello' ]" ADD foo ./bar/baz RUN sh -c "[ $(cat /test1/test2/bar/baz) = 'hello' ]" COPY foo ./bar/baz2 RUN sh -c "[ $(cat /test1/test2/bar/baz2) = 'hello' ]" WORKDIR .. COPY foo ./ RUN sh -c "[ $(cat /test1/foo) = 'hello' ]" COPY foo /test3/ RUN sh -c "[ $(cat /test3/foo) = 'hello' ]" WORKDIR /test4 COPY . . RUN sh -c "[ $(cat /test4/foo) = 'hello' ]" WORKDIR /test5/test6 COPY foo ../ RUN sh -c "[ $(cat /test5/foo) = 'hello' ]" `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte(`hello`), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) } func testDockerfileFromGit(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) gitDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(gitDir) dockerfile := ` FROM busybox AS build RUN echo -n fromgit > foo FROM scratch COPY --from=build foo bar ` err = ioutil.WriteFile(filepath.Join(gitDir, "Dockerfile"), []byte(dockerfile), 0600) require.NoError(t, err) err = runShell(gitDir, "git init", "git config --local user.email test", "git config --local user.name test", "git add Dockerfile", "git commit -m initial", "git branch first", ) require.NoError(t, err) dockerfile += ` COPY --from=build foo bar2 ` err = ioutil.WriteFile(filepath.Join(gitDir, "Dockerfile"), []byte(dockerfile), 0600) require.NoError(t, err) err = runShell(gitDir, "git add Dockerfile", "git commit -m second", "git update-server-info", ) require.NoError(t, err) server := httptest.NewServer(http.FileServer(http.Dir(filepath.Join(gitDir)))) defer server.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "context": server.URL + "/.git#first", }, Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "bar")) require.NoError(t, err) require.Equal(t, "fromgit", string(dt)) _, err = os.Stat(filepath.Join(destDir, "bar2")) require.Error(t, err) require.True(t, errors.Is(err, os.ErrNotExist)) // second request from master branch contains both files destDir, err = ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "context": server.URL + "/.git", }, Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, }, nil) require.NoError(t, err) dt, err = ioutil.ReadFile(filepath.Join(destDir, "bar")) require.NoError(t, err) require.Equal(t, "fromgit", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "bar2")) require.NoError(t, err) require.Equal(t, "fromgit", string(dt)) } func testDockerfileFromHTTP(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) buf := bytes.NewBuffer(nil) w := tar.NewWriter(buf) writeFile := func(fn, dt string) { err := w.WriteHeader(&tar.Header{ Name: fn, Mode: 0600, Size: int64(len(dt)), Typeflag: tar.TypeReg, }) require.NoError(t, err) _, err = w.Write([]byte(dt)) require.NoError(t, err) } writeFile("mydockerfile", `FROM scratch COPY foo bar `) writeFile("foo", "foo-contents") require.NoError(t, w.Flush()) resp := httpserver.Response{ Etag: identity.NewID(), Content: buf.Bytes(), } server := httpserver.NewTestServer(map[string]httpserver.Response{ "/myurl": resp, }) defer server.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "context": server.URL + "/myurl", "filename": "mydockerfile", }, Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "bar")) require.NoError(t, err) require.Equal(t, "foo-contents", string(dt)) } func testMultiStageImplicitFrom(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM scratch COPY --from=busybox /etc/passwd test `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "test")) require.NoError(t, err) require.Contains(t, string(dt), "root") // testing masked image will load actual stage dockerfile = []byte(` FROM busybox AS golang RUN mkdir /usr/bin && echo -n foo > /usr/bin/go FROM scratch COPY --from=golang /usr/bin/go go `) dir, err = tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) destDir, err = ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err = ioutil.ReadFile(filepath.Join(destDir, "go")) require.NoError(t, err) require.Contains(t, string(dt), "foo") } func testMultiStageCaseInsensitive(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM scratch AS STAge0 COPY foo bar FROM scratch AS staGE1 COPY --from=staGE0 bar baz FROM scratch COPY --from=stage1 baz bax `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte("foo-contents"), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, FrontendAttrs: map[string]string{ "target": "Stage1", }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "baz")) require.NoError(t, err) require.Contains(t, string(dt), "foo-contents") } func testLabels(t *testing.T, sb integration.Sandbox) { skipDockerd(t, sb) f := getFrontend(t, sb) dockerfile := []byte(` FROM scratch LABEL foo=bar `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) target := "example.com/moby/dockerfilelabels:test" _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "label:bar": "baz", }, Exports: []client.ExportEntry{ { Type: client.ExporterImage, Attrs: map[string]string{ "name": target, }, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) cdAddress := sb.ContainerdAddress() if cdAddress == "" { t.Skip("rest of test requires containerd worker") } client, err := newContainerd(cdAddress) require.NoError(t, err) defer client.Close() ctx := namespaces.WithNamespace(context.Background(), "buildkit") img, err := client.ImageService().Get(ctx, target) require.NoError(t, err) desc, err := img.Config(ctx, client.ContentStore(), platforms.Default()) require.NoError(t, err) dt, err := content.ReadBlob(ctx, client.ContentStore(), desc) require.NoError(t, err) var ociimg ocispec.Image err = json.Unmarshal(dt, &ociimg) require.NoError(t, err) v, ok := ociimg.Config.Labels["foo"] require.True(t, ok) require.Equal(t, "bar", v) v, ok = ociimg.Config.Labels["bar"] require.True(t, ok) require.Equal(t, "baz", v) } func testOnBuildCleared(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) registry, err := sb.NewRegistry() if errors.Is(err, integration.ErrorRequirements) { t.Skip(err.Error()) } require.NoError(t, err) dockerfile := []byte(` FROM busybox ONBUILD RUN mkdir -p /out && echo -n 11 >> /out/foo `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() target := registry + "/buildkit/testonbuild:base" _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterImage, Attrs: map[string]string{ "push": "true", "name": target, }, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dockerfile = []byte(fmt.Sprintf(` FROM %s `, target)) dir, err = tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) target2 := registry + "/buildkit/testonbuild:child" _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterImage, Attrs: map[string]string{ "push": "true", "name": target2, }, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dockerfile = []byte(fmt.Sprintf(` FROM %s AS base FROM scratch COPY --from=base /out / `, target2)) dir, err = tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "foo")) require.NoError(t, err) require.Equal(t, "11", string(dt)) } func testCacheMultiPlatformImportExport(t *testing.T, sb integration.Sandbox) { skipDockerd(t, sb) f := getFrontend(t, sb) registry, err := sb.NewRegistry() if errors.Is(err, integration.ErrorRequirements) { t.Skip(err.Error()) } require.NoError(t, err) dockerfile := []byte(` FROM --platform=$BUILDPLATFORM busybox AS base ARG TARGETARCH RUN echo -n $TARGETARCH> arch && cat /dev/urandom | head -c 100 | sha256sum > unique FROM scratch COPY --from=base unique / COPY --from=base arch / `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() target := registry + "/buildkit/testexportdf:multi" // exportCache := []client.CacheOptionsEntry{ // { // Type: "registry", // Attrs: map[string]string{"ref": target}, // }, // } // importCache := target exportCache := []client.CacheOptionsEntry{ { Type: "inline", }, } importCache := target + "-img" _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterImage, Attrs: map[string]string{ "push": "true", "name": target + "-img", }, }, }, CacheExports: exportCache, FrontendAttrs: map[string]string{ "platform": "linux/amd64,linux/arm/v7", }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) desc, provider, err := contentutil.ProviderFromRef(target + "-img") require.NoError(t, err) imgMap, err := readIndex(provider, desc) require.NoError(t, err) require.Equal(t, 2, len(imgMap)) require.Equal(t, "amd64", string(imgMap["linux/amd64"].layers[1]["arch"].Data)) dtamd := imgMap["linux/amd64"].layers[0]["unique"].Data dtarm := imgMap["linux/arm/v7"].layers[0]["unique"].Data require.NotEqual(t, dtamd, dtarm) for i := 0; i < 2; i++ { err = c.Prune(context.TODO(), nil, client.PruneAll) require.NoError(t, err) checkAllRemoved(t, c, sb) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "cache-from": importCache, "platform": "linux/amd64,linux/arm/v7", }, Exports: []client.ExportEntry{ { Type: client.ExporterImage, Attrs: map[string]string{ "push": "true", "name": target + "-img", }, }, }, CacheExports: exportCache, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) desc2, provider, err := contentutil.ProviderFromRef(target + "-img") require.NoError(t, err) require.Equal(t, desc.Digest, desc2.Digest) imgMap, err = readIndex(provider, desc2) require.NoError(t, err) require.Equal(t, 2, len(imgMap)) require.Equal(t, "arm", string(imgMap["linux/arm/v7"].layers[1]["arch"].Data)) dtamd2 := imgMap["linux/amd64"].layers[0]["unique"].Data dtarm2 := imgMap["linux/arm/v7"].layers[0]["unique"].Data require.Equal(t, string(dtamd), string(dtamd2)) require.Equal(t, string(dtarm), string(dtarm2)) } } func testCacheImportExport(t *testing.T, sb integration.Sandbox) { skipDockerd(t, sb) f := getFrontend(t, sb) registry, err := sb.NewRegistry() if errors.Is(err, integration.ErrorRequirements) { t.Skip(err.Error()) } require.NoError(t, err) dockerfile := []byte(` FROM busybox AS base COPY foo const #RUN echo -n foobar > const RUN cat /dev/urandom | head -c 100 | sha256sum > unique FROM scratch COPY --from=base const / COPY --from=base unique / `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte("foobar"), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) target := registry + "/buildkit/testexportdf:latest" _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, CacheExports: []client.CacheOptionsEntry{ { Type: "registry", Attrs: map[string]string{"ref": target}, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "const")) require.NoError(t, err) require.Equal(t, "foobar", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "unique")) require.NoError(t, err) err = c.Prune(context.TODO(), nil, client.PruneAll) require.NoError(t, err) checkAllRemoved(t, c, sb) destDir, err = ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "cache-from": target, }, Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) dt2, err := ioutil.ReadFile(filepath.Join(destDir, "const")) require.NoError(t, err) require.Equal(t, "foobar", string(dt2)) dt2, err = ioutil.ReadFile(filepath.Join(destDir, "unique")) require.NoError(t, err) require.Equal(t, string(dt), string(dt2)) destDir, err = ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) } func testReproducibleIDs(t *testing.T, sb integration.Sandbox) { skipDockerd(t, sb) f := getFrontend(t, sb) dockerfile := []byte(` FROM busybox ENV foo=bar COPY foo / RUN echo bar > bar `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte("foo-contents"), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) target := "example.com/moby/dockerfileids:test" opt := client.SolveOpt{ FrontendAttrs: map[string]string{}, Exports: []client.ExportEntry{ { Type: client.ExporterImage, Attrs: map[string]string{ "name": target, }, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, } _, err = f.Solve(context.TODO(), c, opt, nil) require.NoError(t, err) target2 := "example.com/moby/dockerfileids2:test" opt.Exports[0].Attrs["name"] = target2 _, err = f.Solve(context.TODO(), c, opt, nil) require.NoError(t, err) cdAddress := sb.ContainerdAddress() if cdAddress == "" { t.Skip("rest of test requires containerd worker") } client, err := newContainerd(cdAddress) require.NoError(t, err) defer client.Close() ctx := namespaces.WithNamespace(context.Background(), "buildkit") img, err := client.ImageService().Get(ctx, target) require.NoError(t, err) img2, err := client.ImageService().Get(ctx, target2) require.NoError(t, err) require.Equal(t, img.Target, img2.Target) } func testImportExportReproducibleIDs(t *testing.T, sb integration.Sandbox) { cdAddress := sb.ContainerdAddress() if cdAddress == "" { t.Skip("test requires containerd worker") } f := getFrontend(t, sb) registry, err := sb.NewRegistry() if errors.Is(err, integration.ErrorRequirements) { t.Skip(err.Error()) } require.NoError(t, err) dockerfile := []byte(` FROM busybox ENV foo=bar COPY foo / RUN echo bar > bar `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte("foobar"), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) target := "example.com/moby/dockerfileexpids:test" cacheTarget := registry + "/test/dockerfileexpids:cache" opt := client.SolveOpt{ FrontendAttrs: map[string]string{}, Exports: []client.ExportEntry{ { Type: client.ExporterImage, Attrs: map[string]string{ "name": target, }, }, }, CacheExports: []client.CacheOptionsEntry{ { Type: "registry", Attrs: map[string]string{"ref": cacheTarget}, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, } ctd, err := newContainerd(cdAddress) require.NoError(t, err) defer ctd.Close() ctx := namespaces.WithNamespace(context.Background(), "buildkit") _, err = f.Solve(context.TODO(), c, opt, nil) require.NoError(t, err) img, err := ctd.ImageService().Get(ctx, target) require.NoError(t, err) err = ctd.ImageService().Delete(ctx, target) require.NoError(t, err) err = c.Prune(context.TODO(), nil, client.PruneAll) require.NoError(t, err) checkAllRemoved(t, c, sb) target2 := "example.com/moby/dockerfileexpids2:test" opt.Exports[0].Attrs["name"] = target2 opt.FrontendAttrs["cache-from"] = cacheTarget _, err = f.Solve(context.TODO(), c, opt, nil) require.NoError(t, err) img2, err := ctd.ImageService().Get(ctx, target2) require.NoError(t, err) require.Equal(t, img.Target, img2.Target) } func testNoCache(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM busybox AS s0 RUN cat /dev/urandom | head -c 100 | sha256sum | tee unique FROM busybox AS s1 RUN cat /dev/urandom | head -c 100 | sha256sum | tee unique2 FROM scratch COPY --from=s0 unique / COPY --from=s1 unique2 / `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) opt := client.SolveOpt{ FrontendAttrs: map[string]string{}, Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, } _, err = f.Solve(context.TODO(), c, opt, nil) require.NoError(t, err) destDir2, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) opt.FrontendAttrs["no-cache"] = "" opt.Exports[0].OutputDir = destDir2 _, err = f.Solve(context.TODO(), c, opt, nil) require.NoError(t, err) unique1Dir1, err := ioutil.ReadFile(filepath.Join(destDir, "unique")) require.NoError(t, err) unique1Dir2, err := ioutil.ReadFile(filepath.Join(destDir2, "unique")) require.NoError(t, err) unique2Dir1, err := ioutil.ReadFile(filepath.Join(destDir, "unique2")) require.NoError(t, err) unique2Dir2, err := ioutil.ReadFile(filepath.Join(destDir2, "unique2")) require.NoError(t, err) require.NotEqual(t, string(unique1Dir1), string(unique1Dir2)) require.NotEqual(t, string(unique2Dir1), string(unique2Dir2)) destDir3, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) opt.FrontendAttrs["no-cache"] = "s1" opt.Exports[0].OutputDir = destDir3 _, err = f.Solve(context.TODO(), c, opt, nil) require.NoError(t, err) unique1Dir3, err := ioutil.ReadFile(filepath.Join(destDir3, "unique")) require.NoError(t, err) unique2Dir3, err := ioutil.ReadFile(filepath.Join(destDir3, "unique2")) require.NoError(t, err) require.Equal(t, string(unique1Dir2), string(unique1Dir3)) require.NotEqual(t, string(unique2Dir1), string(unique2Dir3)) } func testPlatformArgsImplicit(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(fmt.Sprintf(` FROM scratch AS build-%s COPY foo bar FROM build-${TARGETOS} COPY foo2 bar2 `, runtime.GOOS)) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte("d0"), 0600), fstest.CreateFile("foo2", []byte("d1"), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) opt := client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, } _, err = f.Solve(context.TODO(), c, opt, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "bar")) require.NoError(t, err) require.Equal(t, "d0", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "bar2")) require.NoError(t, err) require.Equal(t, "d1", string(dt)) } func testPlatformArgsExplicit(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM --platform=$BUILDPLATFORM busybox AS build ARG TARGETPLATFORM ARG TARGETOS RUN mkdir /out && echo -n $TARGETPLATFORM > /out/platform && echo -n $TARGETOS > /out/os FROM scratch COPY --from=build out . `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) opt := client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, FrontendAttrs: map[string]string{ "platform": "darwin/ppc64le", "build-arg:TARGETOS": "freebsd", }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, } _, err = f.Solve(context.TODO(), c, opt, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "platform")) require.NoError(t, err) require.Equal(t, "darwin/ppc64le", string(dt)) dt, err = ioutil.ReadFile(filepath.Join(destDir, "os")) require.NoError(t, err) require.Equal(t, "freebsd", string(dt)) } func testBuiltinArgs(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM busybox AS build ARG FOO ARG BAR ARG BAZ=bazcontent RUN echo -n $HTTP_PROXY::$NO_PROXY::$FOO::$BAR::$BAZ > /out FROM scratch COPY --from=build /out / `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) opt := client.SolveOpt{ FrontendAttrs: map[string]string{ "build-arg:FOO": "foocontents", "build-arg:http_proxy": "hpvalue", "build-arg:NO_PROXY": "npvalue", }, Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, } _, err = f.Solve(context.TODO(), c, opt, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "out")) require.NoError(t, err) require.Equal(t, "hpvalue::npvalue::foocontents::::bazcontent", string(dt)) // repeat with changed default args should match the old cache destDir, err = ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) opt = client.SolveOpt{ FrontendAttrs: map[string]string{ "build-arg:FOO": "foocontents", "build-arg:http_proxy": "hpvalue2", }, Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, } _, err = f.Solve(context.TODO(), c, opt, nil) require.NoError(t, err) dt, err = ioutil.ReadFile(filepath.Join(destDir, "out")) require.NoError(t, err) require.Equal(t, "hpvalue::npvalue::foocontents::::bazcontent", string(dt)) // changing actual value invalidates cache destDir, err = ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) opt = client.SolveOpt{ FrontendAttrs: map[string]string{ "build-arg:FOO": "foocontents2", "build-arg:http_proxy": "hpvalue2", }, Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, } _, err = f.Solve(context.TODO(), c, opt, nil) require.NoError(t, err) dt, err = ioutil.ReadFile(filepath.Join(destDir, "out")) require.NoError(t, err) require.Equal(t, "hpvalue2::::foocontents2::::bazcontent", string(dt)) } func testTarContext(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) dockerfile := []byte(` FROM scratch COPY foo / `) foo := []byte("contents") buf := bytes.NewBuffer(nil) tw := tar.NewWriter(buf) err := tw.WriteHeader(&tar.Header{ Name: "Dockerfile", Typeflag: tar.TypeReg, Size: int64(len(dockerfile)), Mode: 0644, }) require.NoError(t, err) _, err = tw.Write(dockerfile) require.NoError(t, err) err = tw.WriteHeader(&tar.Header{ Name: "foo", Typeflag: tar.TypeReg, Size: int64(len(foo)), Mode: 0644, }) require.NoError(t, err) _, err = tw.Write(foo) require.NoError(t, err) err = tw.Close() require.NoError(t, err) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() up := uploadprovider.New() url := up.Add(buf) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), "context": url, }, Session: []session.Attachable{up}, }, nil) require.NoError(t, err) } func testTarContextExternalDockerfile(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) isFileOp := getFileOp(t, sb) foo := []byte("contents") buf := bytes.NewBuffer(nil) tw := tar.NewWriter(buf) err := tw.WriteHeader(&tar.Header{ Name: "sub/dir/foo", Typeflag: tar.TypeReg, Size: int64(len(foo)), Mode: 0644, }) require.NoError(t, err) _, err = tw.Write(foo) require.NoError(t, err) err = tw.Close() require.NoError(t, err) dockerfile := []byte(` FROM scratch COPY foo bar `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() up := uploadprovider.New() url := up.Add(buf) // repeat with changed default args should match the old cache destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), "context": url, "dockerfilekey": builder.DefaultLocalNameDockerfile, "contextsubdir": "sub/dir", }, Session: []session.Attachable{up}, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, }, Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, }, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "bar")) require.NoError(t, err) require.Equal(t, string(dt), "contents") } func testFrontendUseForwardedSolveResults(t *testing.T, sb integration.Sandbox) { c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() dockerfile := []byte(` FROM scratch COPY foo foo2 `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), fstest.CreateFile("foo", []byte("data"), 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) frontend := func(ctx context.Context, c gateway.Client) (*gateway.Result, error) { res, err := c.Solve(ctx, gateway.SolveRequest{ Frontend: "dockerfile.v0", }) if err != nil { return nil, err } ref, err := res.SingleRef() if err != nil { return nil, err } st2, err := ref.ToState() if err != nil { return nil, err } st := llb.Scratch().File( llb.Copy(st2, "foo2", "foo3"), ) def, err := st.Marshal(context.TODO()) if err != nil { return nil, err } return c.Solve(ctx, gateway.SolveRequest{ Definition: def.ToPB(), }) } destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) _, err = c.Build(context.TODO(), client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, "", frontend, nil) require.NoError(t, err) dt, err := ioutil.ReadFile(filepath.Join(destDir, "foo3")) require.NoError(t, err) require.Equal(t, dt, []byte("data")) } func testFrontendInputs(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() destDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) defer os.RemoveAll(destDir) outMount := llb.Image("busybox").Run( llb.Shlex(`sh -c "cat /dev/urandom | head -c 100 | sha256sum > /out/foo"`), ).AddMount("/out", llb.Scratch()) def, err := outMount.Marshal(context.TODO()) require.NoError(t, err) _, err = c.Solve(context.TODO(), def, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, }, nil) require.NoError(t, err) expected, err := ioutil.ReadFile(filepath.Join(destDir, "foo")) require.NoError(t, err) dockerfile := []byte(` FROM scratch COPY foo foo2 `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) _, err = f.Solve(context.TODO(), c, client.SolveOpt{ Exports: []client.ExportEntry{ { Type: client.ExporterLocal, OutputDir: destDir, }, }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, }, FrontendInputs: map[string]llb.State{ builder.DefaultLocalNameContext: outMount, }, }, nil) require.NoError(t, err) actual, err := ioutil.ReadFile(filepath.Join(destDir, "foo2")) require.NoError(t, err) require.Equal(t, expected, actual) } func testFrontendSubrequests(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() dockerfile := []byte(` FROM scratch COPY Dockerfile Dockerfile `) if gf, ok := f.(*gatewayFrontend); ok { dockerfile = []byte(fmt.Sprintf("#syntax=%s\n\n%s", gf.gw, dockerfile)) } dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) called := false frontend := func(ctx context.Context, c gateway.Client) (*gateway.Result, error) { reqs, err := subrequests.Describe(ctx, c) require.NoError(t, err) require.True(t, len(reqs) > 0) hasDescribe := false for _, req := range reqs { if req.Name == "frontend.subrequests.describe" { hasDescribe = true require.Equal(t, subrequests.RequestType("rpc"), req.Type) require.NotEqual(t, req.Version, "") require.True(t, len(req.Metadata) > 0) } } require.True(t, hasDescribe) _, err = c.Solve(ctx, gateway.SolveRequest{ FrontendOpt: map[string]string{ "requestid": "frontend.subrequests.notexist", "frontend.caps": "moby.buildkit.frontend.subrequests", }, Frontend: "dockerfile.v0", }) require.Error(t, err) var reqErr *errdefs.UnsupportedSubrequestError require.True(t, errors.As(err, &reqErr)) require.Equal(t, "frontend.subrequests.notexist", reqErr.GetName()) _, err = c.Solve(ctx, gateway.SolveRequest{ FrontendOpt: map[string]string{ "frontend.caps": "moby.buildkit.frontend.notexistcap", }, Frontend: "dockerfile.v0", }) require.Error(t, err) var capErr *errdefs.UnsupportedFrontendCapError require.True(t, errors.As(err, &capErr)) require.Equal(t, "moby.buildkit.frontend.notexistcap", capErr.GetName()) called = true return nil, nil } _, err = c.Build(context.TODO(), client.SolveOpt{ LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, }, }, "", frontend, nil) require.NoError(t, err) require.True(t, called) } // moby/buildkit#1301 func testDockefileCheckHostname(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) dockerfile := []byte(` FROM busybox RUN cat /etc/hosts | grep testtest RUN echo $HOSTNAME | grep testtest RUN echo $(hostname) | grep testtest `) dir, err := tmpdir( fstest.CreateFile("Dockerfile", dockerfile, 0600), ) require.NoError(t, err) defer os.RemoveAll(dir) c, err := client.New(context.TODO(), sb.Address()) require.NoError(t, err) defer c.Close() _, err = f.Solve(context.TODO(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ "hostname": "testtest", }, LocalDirs: map[string]string{ builder.DefaultLocalNameDockerfile: dir, builder.DefaultLocalNameContext: dir, }, }, nil) require.NoError(t, err) } func tmpdir(appliers ...fstest.Applier) (string, error) { tmpdir, err := ioutil.TempDir("", "buildkit-dockerfile") if err != nil { return "", err } if err := fstest.Apply(appliers...).Apply(tmpdir); err != nil { return "", err } return tmpdir, nil } func runShell(dir string, cmds ...string) error { for _, args := range cmds { var cmd *exec.Cmd if runtime.GOOS == "windows" { cmd = exec.Command("powershell", "-command", args) } else { cmd = exec.Command("sh", "-c", args) } cmd.Dir = dir if err := cmd.Run(); err != nil { return errors.Wrapf(err, "error running %v", args) } } return nil } func checkAllRemoved(t *testing.T, c *client.Client, sb integration.Sandbox) { retries := 0 for { require.True(t, 20 > retries) retries++ du, err := c.DiskUsage(context.TODO()) require.NoError(t, err) if len(du) > 0 { time.Sleep(500 * time.Millisecond) continue } break } } func checkAllReleasable(t *testing.T, c *client.Client, sb integration.Sandbox, checkContent bool) { retries := 0 loop0: for { require.True(t, 20 > retries) retries++ du, err := c.DiskUsage(context.TODO()) require.NoError(t, err) for _, d := range du { if d.InUse { time.Sleep(500 * time.Millisecond) continue loop0 } } break } err := c.Prune(context.TODO(), nil, client.PruneAll) require.NoError(t, err) du, err := c.DiskUsage(context.TODO()) require.NoError(t, err) require.Equal(t, 0, len(du)) // examine contents of exported tars (requires containerd) cdAddress := sb.ContainerdAddress() if cdAddress == "" { return } // TODO: make public pull helper function so this can be checked for standalone as well client, err := newContainerd(cdAddress) require.NoError(t, err) defer client.Close() ctx := namespaces.WithNamespace(context.Background(), "buildkit") snapshotService := client.SnapshotService("overlayfs") retries = 0 for { count := 0 err = snapshotService.Walk(ctx, func(context.Context, snapshots.Info) error { count++ return nil }) require.NoError(t, err) if count == 0 { break } require.True(t, 20 > retries) retries++ time.Sleep(500 * time.Millisecond) } if !checkContent { return } retries = 0 for { count := 0 err = client.ContentStore().Walk(ctx, func(content.Info) error { count++ return nil }) require.NoError(t, err) if count == 0 { break } require.True(t, 20 > retries) retries++ time.Sleep(500 * time.Millisecond) } } func newContainerd(cdAddress string) (*containerd.Client, error) { return containerd.New(cdAddress, containerd.WithTimeout(60*time.Second)) } func dfCmdArgs(ctx, dockerfile, args string) (string, string) { traceFile := filepath.Join(os.TempDir(), "trace"+identity.NewID()) return fmt.Sprintf("build --progress=plain %s --local context=%s --local dockerfile=%s --trace=%s", args, ctx, dockerfile, traceFile), traceFile } type builtinFrontend struct{} var _ frontend = &builtinFrontend{} func (f *builtinFrontend) Solve(ctx context.Context, c *client.Client, opt client.SolveOpt, statusChan chan *client.SolveStatus) (*client.SolveResponse, error) { opt.Frontend = "dockerfile.v0" return c.Solve(ctx, nil, opt, statusChan) } func (f *builtinFrontend) DFCmdArgs(ctx, dockerfile string) (string, string) { return dfCmdArgs(ctx, dockerfile, "--frontend dockerfile.v0") } func (f *builtinFrontend) RequiresBuildctl(t *testing.T) {} type clientFrontend struct{} var _ frontend = &clientFrontend{} func (f *clientFrontend) Solve(ctx context.Context, c *client.Client, opt client.SolveOpt, statusChan chan *client.SolveStatus) (*client.SolveResponse, error) { return c.Build(ctx, opt, "", builder.Build, statusChan) } func (f *clientFrontend) DFCmdArgs(ctx, dockerfile string) (string, string) { return "", "" } func (f *clientFrontend) RequiresBuildctl(t *testing.T) { t.Skip() } type gatewayFrontend struct { gw string } var _ frontend = &gatewayFrontend{} func (f *gatewayFrontend) Solve(ctx context.Context, c *client.Client, opt client.SolveOpt, statusChan chan *client.SolveStatus) (*client.SolveResponse, error) { opt.Frontend = "gateway.v0" if opt.FrontendAttrs == nil { opt.FrontendAttrs = make(map[string]string) } opt.FrontendAttrs["source"] = f.gw return c.Solve(ctx, nil, opt, statusChan) } func (f *gatewayFrontend) DFCmdArgs(ctx, dockerfile string) (string, string) { return dfCmdArgs(ctx, dockerfile, "--frontend gateway.v0 --opt=source="+f.gw) } func (f *gatewayFrontend) RequiresBuildctl(t *testing.T) {} func getFrontend(t *testing.T, sb integration.Sandbox) frontend { v := sb.Value("frontend") require.NotNil(t, v) fn, ok := v.(frontend) require.True(t, ok) return fn } func getFileOp(t *testing.T, sb integration.Sandbox) bool { v := sb.Value("fileop") require.NotNil(t, v) vv, ok := v.(bool) require.True(t, ok) return vv } type nopWriteCloser struct { io.Writer } func (nopWriteCloser) Close() error { return nil } type secModeSandbox struct{} func (*secModeSandbox) UpdateConfigFile(in string) string { return in } type secModeInsecure struct{} func (*secModeInsecure) UpdateConfigFile(in string) string { return in + "\n\ninsecure-entitlements = [\"security.insecure\"]\n" } var securityInsecureGranted integration.ConfigUpdater = &secModeInsecure{} var securityInsecureDenied integration.ConfigUpdater = &secModeSandbox{} type networkModeHost struct{} func (*networkModeHost) UpdateConfigFile(in string) string { return in + "\n\ninsecure-entitlements = [\"network.host\"]\n" } type networkModeSandbox struct{} func (*networkModeSandbox) UpdateConfigFile(in string) string { return in } var networkHostGranted integration.ConfigUpdater = &networkModeHost{} var networkHostDenied integration.ConfigUpdater = &networkModeSandbox{} func fixedWriteCloser(wc io.WriteCloser) func(map[string]string) (io.WriteCloser, error) { return func(map[string]string) (io.WriteCloser, error) { return wc, nil } } type imageInfo struct { desc ocispec.Descriptor layers []map[string]*testutil.TarItem } func readIndex(p content.Provider, desc ocispec.Descriptor) (map[string]*imageInfo, error) { ctx := context.TODO() dt, err := content.ReadBlob(ctx, p, desc) if err != nil { return nil, err } var idx ocispec.Index if err := json.Unmarshal(dt, &idx); err != nil { return nil, err } mi := map[string]*imageInfo{} for _, m := range idx.Manifests { img, err := readImage(p, m) if err != nil { return nil, err } mi[platforms.Format(*m.Platform)] = img } return mi, nil } func readImage(p content.Provider, desc ocispec.Descriptor) (*imageInfo, error) { ii := &imageInfo{desc: desc} ctx := context.TODO() dt, err := content.ReadBlob(ctx, p, desc) if err != nil { return nil, err } var mfst ocispec.Manifest if err := json.Unmarshal(dt, &mfst); err != nil { return nil, err } for _, l := range mfst.Layers { dt, err := content.ReadBlob(ctx, p, l) if err != nil { return nil, err } m, err := testutil.ReadTarToMap(dt, true) if err != nil { return nil, err } ii.layers = append(ii.layers, m) } return ii, nil }