buildkit/frontend/dockerfile/dockerfile_test.go

5213 lines
132 KiB
Go

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
}