mirror of
https://github.com/moby/buildkit.git
synced 2026-11-06 05:16:45 +00:00
Move Dockerfile integration tests into topic-specific files while preserving registration order and test bodies. Keep shared helpers centralized where they are used across topics, and move topic-local helpers with their callers. Fold small one-off buckets into the broader ADD/COPY groupings and keep same-file-only registration lists next to their tests. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
400 lines
10 KiB
Go
400 lines
10 KiB
Go
package dockerfile
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/containerd/continuity/fs/fstest"
|
|
"github.com/containerd/platforms"
|
|
"github.com/moby/buildkit/client"
|
|
"github.com/moby/buildkit/frontend/dockerui"
|
|
"github.com/moby/buildkit/session"
|
|
"github.com/moby/buildkit/session/upload/uploadprovider"
|
|
"github.com/moby/buildkit/util/iohelper"
|
|
"github.com/moby/buildkit/util/testutil"
|
|
"github.com/moby/buildkit/util/testutil/integration"
|
|
"github.com/moby/buildkit/util/testutil/workers"
|
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/tonistiigi/fsutil"
|
|
)
|
|
|
|
func testTarExporterBasic(t *testing.T, sb integration.Sandbox) {
|
|
f := getFrontend(t, sb)
|
|
|
|
dockerfile := []byte(integration.UnixOrWindows(
|
|
`
|
|
FROM scratch
|
|
COPY foo foo
|
|
`,
|
|
`
|
|
FROM nanoserver
|
|
COPY foo foo
|
|
`,
|
|
))
|
|
|
|
dir := integration.Tmpdir(
|
|
t,
|
|
fstest.CreateFile("Dockerfile", dockerfile, 0600),
|
|
fstest.CreateFile("foo", []byte("data"), 0600),
|
|
)
|
|
|
|
c, err := client.New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
buf := &bytes.Buffer{}
|
|
_, err = f.Solve(sb.Context(), c, client.SolveOpt{
|
|
Exports: []client.ExportEntry{
|
|
{
|
|
Type: client.ExporterTar,
|
|
Output: fixedWriteCloser(&iohelper.NopWriteCloser{Writer: buf}),
|
|
},
|
|
},
|
|
LocalMounts: map[string]fsutil.FS{
|
|
dockerui.DefaultLocalNameDockerfile: dir,
|
|
dockerui.DefaultLocalNameContext: dir,
|
|
},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
|
|
m, err := testutil.ReadTarToMap(buf.Bytes(), false)
|
|
require.NoError(t, err)
|
|
|
|
mi, ok := m["foo"]
|
|
require.Equal(t, true, ok)
|
|
require.Equal(t, "data", string(mi.Data))
|
|
}
|
|
|
|
func testTarExporterMulti(t *testing.T, sb integration.Sandbox) {
|
|
integration.SkipOnPlatform(t, "windows", "Multi-platform tar export test specifically targets linux/amd64 and darwin/amd64 platforms as feature it's supported on Windows")
|
|
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 := integration.Tmpdir(
|
|
t,
|
|
fstest.CreateFile("Dockerfile", dockerfile, 0600),
|
|
fstest.CreateFile("foo", []byte("data"), 0600),
|
|
fstest.CreateFile("bar", []byte("data2"), 0600),
|
|
)
|
|
|
|
c, err := client.New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
buf := &bytes.Buffer{}
|
|
_, err = f.Solve(sb.Context(), c, client.SolveOpt{
|
|
Exports: []client.ExportEntry{
|
|
{
|
|
Type: client.ExporterTar,
|
|
Output: fixedWriteCloser(&iohelper.NopWriteCloser{Writer: buf}),
|
|
},
|
|
},
|
|
LocalMounts: map[string]fsutil.FS{
|
|
dockerui.DefaultLocalNameDockerfile: dir,
|
|
dockerui.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(sb.Context(), c, client.SolveOpt{
|
|
Exports: []client.ExportEntry{
|
|
{
|
|
Type: client.ExporterTar,
|
|
Output: fixedWriteCloser(&iohelper.NopWriteCloser{Writer: buf}),
|
|
},
|
|
},
|
|
FrontendAttrs: map[string]string{
|
|
"platform": "linux/amd64,darwin/amd64",
|
|
},
|
|
LocalMounts: map[string]fsutil.FS{
|
|
dockerui.DefaultLocalNameDockerfile: dir,
|
|
dockerui.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 testExportMultiPlatform(t *testing.T, sb integration.Sandbox) {
|
|
integration.SkipOnPlatform(t, "windows", "Multi-platform builds from scratch fail on Windows: 'number of mounts should always be 1 for Windows layers'")
|
|
workers.CheckFeatureCompat(t, sb, workers.FeatureOCIExporter, workers.FeatureMultiPlatform)
|
|
f := getFrontend(t, sb)
|
|
|
|
dockerfile := []byte(`
|
|
FROM scratch
|
|
ARG TARGETARCH
|
|
ARG TARGETPLATFORM
|
|
LABEL target=$TARGETPLATFORM
|
|
COPY arch-$TARGETARCH whoami
|
|
`)
|
|
|
|
dir := integration.Tmpdir(
|
|
t,
|
|
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),
|
|
)
|
|
|
|
c, err := client.New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
destDir := t.TempDir()
|
|
|
|
_, err = f.Solve(sb.Context(), c, client.SolveOpt{
|
|
LocalMounts: map[string]fsutil.FS{
|
|
dockerui.DefaultLocalNameDockerfile: dir,
|
|
dockerui.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 := os.ReadFile(filepath.Join(destDir, "windows_amd64/whoami"))
|
|
require.NoError(t, err)
|
|
require.Equal(t, "i am amd64", string(dt))
|
|
|
|
dt, err = os.ReadFile(filepath.Join(destDir, "linux_arm_v7/whoami"))
|
|
require.NoError(t, err)
|
|
require.Equal(t, "i am arm", string(dt))
|
|
|
|
dt, err = os.ReadFile(filepath.Join(destDir, "linux_s390x/whoami"))
|
|
require.NoError(t, err)
|
|
require.Equal(t, "i am s390x", string(dt))
|
|
|
|
// repeat with oci exporter
|
|
|
|
destDir = t.TempDir()
|
|
|
|
out := filepath.Join(destDir, "out.tar")
|
|
outW, err := os.Create(out)
|
|
require.NoError(t, err)
|
|
|
|
_, err = f.Solve(sb.Context(), c, client.SolveOpt{
|
|
LocalMounts: map[string]fsutil.FS{
|
|
dockerui.DefaultLocalNameDockerfile: dir,
|
|
dockerui.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 = os.ReadFile(filepath.Join(destDir, "out.tar"))
|
|
require.NoError(t, err)
|
|
|
|
m, err := testutil.ReadTarToMap(dt, false)
|
|
require.NoError(t, err)
|
|
|
|
var idx ocispecs.Index
|
|
err = json.Unmarshal(m[ocispecs.ImageIndexFile].Data, &idx)
|
|
require.NoError(t, err)
|
|
|
|
mlistHex := idx.Manifests[0].Digest.Hex()
|
|
|
|
idx = ocispecs.Index{}
|
|
err = json.Unmarshal(m[ocispecs.ImageBlobsDir+"/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 ocispecs.Manifest
|
|
err = json.Unmarshal(m[ocispecs.ImageBlobsDir+"/sha256/"+idx.Manifests[i].Digest.Hex()].Data, &mfst)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 1, len(mfst.Layers))
|
|
|
|
m2, err := testutil.ReadTarToMap(m[ocispecs.ImageBlobsDir+"/sha256/"+mfst.Layers[0].Digest.Hex()].Data, true)
|
|
require.NoError(t, err)
|
|
require.Equal(t, exp.dt, string(m2["whoami"].Data))
|
|
|
|
var img ocispecs.Image
|
|
err = json.Unmarshal(m[ocispecs.ImageBlobsDir+"/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)
|
|
})
|
|
}
|
|
}
|
|
|
|
func testTarContext(t *testing.T, sb integration.Sandbox) {
|
|
f := getFrontend(t, sb)
|
|
|
|
imgName := integration.UnixOrWindows("scratch", "nanoserver")
|
|
dockerfile := fmt.Appendf(nil, `
|
|
FROM %s
|
|
COPY foo /`, imgName)
|
|
|
|
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(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
up := uploadprovider.New()
|
|
url := up.Add(io.NopCloser(buf))
|
|
|
|
_, err = f.Solve(sb.Context(), c, client.SolveOpt{
|
|
FrontendAttrs: map[string]string{
|
|
"context": url,
|
|
},
|
|
Session: []session.Attachable{up},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func testTarContextExternalDockerfile(t *testing.T, sb integration.Sandbox) {
|
|
f := getFrontend(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)
|
|
|
|
imgName := integration.UnixOrWindows("scratch", "nanoserver")
|
|
dockerfile := fmt.Appendf(nil, `
|
|
FROM %s
|
|
COPY foo bar
|
|
`, imgName)
|
|
dir := integration.Tmpdir(
|
|
t,
|
|
fstest.CreateFile("Dockerfile", dockerfile, 0600),
|
|
)
|
|
|
|
c, err := client.New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
up := uploadprovider.New()
|
|
url := up.Add(io.NopCloser(buf))
|
|
|
|
// repeat with changed default args should match the old cache
|
|
destDir := t.TempDir()
|
|
|
|
_, err = f.Solve(sb.Context(), c, client.SolveOpt{
|
|
FrontendAttrs: map[string]string{
|
|
"context": url,
|
|
"dockerfilekey": dockerui.DefaultLocalNameDockerfile,
|
|
"contextsubdir": "sub/dir",
|
|
},
|
|
Session: []session.Attachable{up},
|
|
LocalMounts: map[string]fsutil.FS{
|
|
dockerui.DefaultLocalNameDockerfile: dir,
|
|
},
|
|
Exports: []client.ExportEntry{
|
|
{
|
|
Type: client.ExporterLocal,
|
|
OutputDir: destDir,
|
|
},
|
|
},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
|
|
dt, err := os.ReadFile(filepath.Join(destDir, "bar"))
|
|
require.NoError(t, err)
|
|
require.Equal(t, "contents", string(dt))
|
|
}
|