mirror of
https://github.com/moby/buildkit.git
synced 2026-08-10 17:14:08 +00:00
dockerfile: add tests for multi-platform exports
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
@@ -2,12 +2,9 @@ package client
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -27,6 +24,7 @@ import (
|
||||
"github.com/moby/buildkit/identity"
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/moby/buildkit/session/secrets/secretsprovider"
|
||||
"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"
|
||||
@@ -486,25 +484,25 @@ func testOCIExporter(t *testing.T, sb integration.Sandbox) {
|
||||
dt, err := ioutil.ReadFile(out)
|
||||
require.NoError(t, err)
|
||||
|
||||
m, err := readTarToMap(dt, false)
|
||||
m, err := testutil.ReadTarToMap(dt, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, ok := m["oci-layout"]
|
||||
require.True(t, ok)
|
||||
|
||||
var index ocispec.Index
|
||||
err = json.Unmarshal(m["index.json"].data, &index)
|
||||
err = json.Unmarshal(m["index.json"].Data, &index)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, index.SchemaVersion)
|
||||
require.Equal(t, 1, len(index.Manifests))
|
||||
|
||||
var mfst ocispec.Manifest
|
||||
err = json.Unmarshal(m["blobs/sha256/"+index.Manifests[0].Digest.Hex()].data, &mfst)
|
||||
err = json.Unmarshal(m["blobs/sha256/"+index.Manifests[0].Digest.Hex()].Data, &mfst)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(mfst.Layers))
|
||||
|
||||
var ociimg ocispec.Image
|
||||
err = json.Unmarshal(m["blobs/sha256/"+mfst.Config.Digest.Hex()].data, &ociimg)
|
||||
err = json.Unmarshal(m["blobs/sha256/"+mfst.Config.Digest.Hex()].Data, &ociimg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "layers", ociimg.RootFS.Type)
|
||||
require.Equal(t, 2, len(ociimg.RootFS.DiffIDs))
|
||||
@@ -523,7 +521,7 @@ func testOCIExporter(t *testing.T, sb integration.Sandbox) {
|
||||
RepoTags []string
|
||||
Layers []string
|
||||
}
|
||||
err = json.Unmarshal(m["manifest.json"].data, &dockerMfst)
|
||||
err = json.Unmarshal(m["manifest.json"].Data, &dockerMfst)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(dockerMfst))
|
||||
|
||||
@@ -692,22 +690,22 @@ func testBuildPushAndValidate(t *testing.T, sb integration.Sandbox) {
|
||||
dt, err = content.ReadBlob(ctx, img.ContentStore(), ocispec.Descriptor{Digest: mfst.Layers[0].Digest})
|
||||
require.NoError(t, err)
|
||||
|
||||
m, err := readTarToMap(dt, true)
|
||||
m, err := testutil.ReadTarToMap(dt, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
item, ok := m["foo/"]
|
||||
require.True(t, ok)
|
||||
require.Equal(t, int32(item.header.Typeflag), tar.TypeDir)
|
||||
require.Equal(t, 0741, int(item.header.Mode&0777))
|
||||
require.Equal(t, int32(item.Header.Typeflag), tar.TypeDir)
|
||||
require.Equal(t, 0741, int(item.Header.Mode&0777))
|
||||
|
||||
item, ok = m["foo/sub/"]
|
||||
require.True(t, ok)
|
||||
require.Equal(t, int32(item.header.Typeflag), tar.TypeDir)
|
||||
require.Equal(t, int32(item.Header.Typeflag), tar.TypeDir)
|
||||
|
||||
item, ok = m["foo/sub/bar"]
|
||||
require.True(t, ok)
|
||||
require.Equal(t, int32(item.header.Typeflag), tar.TypeReg)
|
||||
require.Equal(t, []byte("first"), item.data)
|
||||
require.Equal(t, int32(item.Header.Typeflag), tar.TypeReg)
|
||||
require.Equal(t, []byte("first"), item.Data)
|
||||
|
||||
_, ok = m["foo/sub/baz"]
|
||||
require.False(t, ok)
|
||||
@@ -715,22 +713,22 @@ func testBuildPushAndValidate(t *testing.T, sb integration.Sandbox) {
|
||||
dt, err = content.ReadBlob(ctx, img.ContentStore(), ocispec.Descriptor{Digest: mfst.Layers[1].Digest})
|
||||
require.NoError(t, err)
|
||||
|
||||
m, err = readTarToMap(dt, true)
|
||||
m, err = testutil.ReadTarToMap(dt, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
item, ok = m["foo/sub/baz"]
|
||||
require.True(t, ok)
|
||||
require.Equal(t, int32(item.header.Typeflag), tar.TypeReg)
|
||||
require.Equal(t, []byte("second"), item.data)
|
||||
require.Equal(t, int32(item.Header.Typeflag), tar.TypeReg)
|
||||
require.Equal(t, []byte("second"), item.Data)
|
||||
|
||||
item, ok = m["foo/"]
|
||||
require.True(t, ok)
|
||||
require.Equal(t, int32(item.header.Typeflag), tar.TypeDir)
|
||||
require.Equal(t, 0741, int(item.header.Mode&0777))
|
||||
require.Equal(t, int32(item.Header.Typeflag), tar.TypeDir)
|
||||
require.Equal(t, 0741, int(item.Header.Mode&0777))
|
||||
|
||||
item, ok = m["foo/sub/"]
|
||||
require.True(t, ok)
|
||||
require.Equal(t, int32(item.header.Typeflag), tar.TypeDir)
|
||||
require.Equal(t, int32(item.Header.Typeflag), tar.TypeDir)
|
||||
|
||||
_, ok = m["foo/sub/bar"]
|
||||
require.False(t, ok)
|
||||
@@ -978,15 +976,15 @@ func testDuplicateWhiteouts(t *testing.T, sb integration.Sandbox) {
|
||||
dt, err := ioutil.ReadFile(out)
|
||||
require.NoError(t, err)
|
||||
|
||||
m, err := readTarToMap(dt, false)
|
||||
m, err := testutil.ReadTarToMap(dt, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
var index ocispec.Index
|
||||
err = json.Unmarshal(m["index.json"].data, &index)
|
||||
err = json.Unmarshal(m["index.json"].Data, &index)
|
||||
require.NoError(t, err)
|
||||
|
||||
var mfst ocispec.Manifest
|
||||
err = json.Unmarshal(m["blobs/sha256/"+index.Manifests[0].Digest.Hex()].data, &mfst)
|
||||
err = json.Unmarshal(m["blobs/sha256/"+index.Manifests[0].Digest.Hex()].Data, &mfst)
|
||||
require.NoError(t, err)
|
||||
|
||||
lastLayer := mfst.Layers[len(mfst.Layers)-1]
|
||||
@@ -994,7 +992,7 @@ func testDuplicateWhiteouts(t *testing.T, sb integration.Sandbox) {
|
||||
layer, ok := m["blobs/sha256/"+lastLayer.Digest.Hex()]
|
||||
require.True(t, ok)
|
||||
|
||||
m, err = readTarToMap(layer.data, true)
|
||||
m, err = testutil.ReadTarToMap(layer.Data, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, ok = m[".wh.d0"]
|
||||
@@ -1045,15 +1043,15 @@ func testWhiteoutParentDir(t *testing.T, sb integration.Sandbox) {
|
||||
dt, err := ioutil.ReadFile(out)
|
||||
require.NoError(t, err)
|
||||
|
||||
m, err := readTarToMap(dt, false)
|
||||
m, err := testutil.ReadTarToMap(dt, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
var index ocispec.Index
|
||||
err = json.Unmarshal(m["index.json"].data, &index)
|
||||
err = json.Unmarshal(m["index.json"].Data, &index)
|
||||
require.NoError(t, err)
|
||||
|
||||
var mfst ocispec.Manifest
|
||||
err = json.Unmarshal(m["blobs/sha256/"+index.Manifests[0].Digest.Hex()].data, &mfst)
|
||||
err = json.Unmarshal(m["blobs/sha256/"+index.Manifests[0].Digest.Hex()].Data, &mfst)
|
||||
require.NoError(t, err)
|
||||
|
||||
lastLayer := mfst.Layers[len(mfst.Layers)-1]
|
||||
@@ -1061,7 +1059,7 @@ func testWhiteoutParentDir(t *testing.T, sb integration.Sandbox) {
|
||||
layer, ok := m["blobs/sha256/"+lastLayer.Digest.Hex()]
|
||||
require.True(t, ok)
|
||||
|
||||
m, err = readTarToMap(layer.data, true)
|
||||
m, err = testutil.ReadTarToMap(layer.Data, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, ok = m["foo/.wh.bar"]
|
||||
@@ -1213,46 +1211,6 @@ func requiresLinux(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type tarItem struct {
|
||||
header *tar.Header
|
||||
data []byte
|
||||
}
|
||||
|
||||
func readTarToMap(dt []byte, compressed bool) (map[string]*tarItem, error) {
|
||||
m := map[string]*tarItem{}
|
||||
var r io.Reader = bytes.NewBuffer(dt)
|
||||
if compressed {
|
||||
gz, err := gzip.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer gz.Close()
|
||||
r = gz
|
||||
}
|
||||
tr := tar.NewReader(r)
|
||||
for {
|
||||
h, err := tr.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return m, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := m[h.Name]; ok {
|
||||
return nil, errors.Errorf("duplicate entries for %s", h.Name)
|
||||
}
|
||||
|
||||
var dt []byte
|
||||
if h.Typeflag == tar.TypeReg {
|
||||
dt, err = ioutil.ReadAll(tr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
m[h.Name] = &tarItem{header: h, data: dt}
|
||||
}
|
||||
}
|
||||
|
||||
func checkAllRemoved(t *testing.T, c *Client, sb integration.Sandbox) {
|
||||
retries := 0
|
||||
for {
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/builder"
|
||||
"github.com/moby/buildkit/identity"
|
||||
"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"
|
||||
@@ -72,9 +73,143 @@ func TestIntegration(t *testing.T) {
|
||||
testContextChangeDirToFile,
|
||||
testPlatformArgsImplicit,
|
||||
testPlatformArgsExplicit,
|
||||
testExportMultiPlatform,
|
||||
})
|
||||
}
|
||||
|
||||
func testExportMultiPlatform(t *testing.T, sb integration.Sandbox) {
|
||||
t.Parallel()
|
||||
|
||||
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 = c.Solve(context.TODO(), nil, client.SolveOpt{
|
||||
Frontend: "dockerfile.v0",
|
||||
LocalDirs: map[string]string{
|
||||
builder.LocalNameDockerfile: dir,
|
||||
builder.LocalNameContext: dir,
|
||||
},
|
||||
FrontendAttrs: map[string]string{
|
||||
"platform": "windows/amd64,linux/arm,linux/s390x",
|
||||
},
|
||||
Exporter: client.ExporterLocal,
|
||||
ExporterOutputDir: 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 = c.Solve(context.TODO(), nil, client.SolveOpt{
|
||||
Frontend: "dockerfile.v0",
|
||||
LocalDirs: map[string]string{
|
||||
builder.LocalNameDockerfile: dir,
|
||||
builder.LocalNameContext: dir,
|
||||
},
|
||||
FrontendAttrs: map[string]string{
|
||||
"platform": "windows/amd64,linux/arm/v6,linux/ppc64le",
|
||||
},
|
||||
Exporter: client.ExporterOCI,
|
||||
ExporterOutput: 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) {
|
||||
t.Parallel()
|
||||
|
||||
51
util/testutil/tar.go
Normal file
51
util/testutil/tar.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type TarItem struct {
|
||||
Header *tar.Header
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func ReadTarToMap(dt []byte, compressed bool) (map[string]*TarItem, error) {
|
||||
m := map[string]*TarItem{}
|
||||
var r io.Reader = bytes.NewBuffer(dt)
|
||||
if compressed {
|
||||
gz, err := gzip.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating gzip reader")
|
||||
}
|
||||
defer gz.Close()
|
||||
r = gz
|
||||
}
|
||||
tr := tar.NewReader(r)
|
||||
for {
|
||||
h, err := tr.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return m, nil
|
||||
}
|
||||
return nil, errors.Wrap(err, "error reading tar")
|
||||
}
|
||||
if _, ok := m[h.Name]; ok {
|
||||
return nil, errors.Errorf("duplicate entries for %s", h.Name)
|
||||
}
|
||||
|
||||
var dt []byte
|
||||
if h.Typeflag == tar.TypeReg {
|
||||
dt, err = ioutil.ReadAll(tr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading file")
|
||||
}
|
||||
}
|
||||
m[h.Name] = &TarItem{Header: h, Data: dt}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user