Allow store to docker multiarch builds

We do not allow to load into docker images that are targets another
platform differ from current arch. Assume this is because of no support
of manifest. But we can keep all images in place by adding arch suffix
and using tag without arch suffix to point onto current system arch. It
will help to use images from docker for another arch.

Signed-off-by: Petr Fedchenkov <giggsoff@gmail.com>
This commit is contained in:
Petr Fedchenkov 2022-07-15 15:59:23 +03:00
parent 44dfac2725
commit 39f1649995
No known key found for this signature in database
GPG Key ID: 01AB26025D699586
3 changed files with 65 additions and 35 deletions

View File

@ -191,20 +191,6 @@ func (p Pkg) Build(bos ...BuildOpt) error {
return errors.New("must provide linuxkit build cache directory")
}
// if targeting docker, be sure local arch is a build target
if bo.targetDocker {
var found bool
for _, platform := range bo.platforms {
if platform.Architecture == arch {
found = true
break
}
}
if !found {
return fmt.Errorf("must build for local platform 'linux/%s' when targeting docker", arch)
}
}
if p.git != nil && bo.push && bo.release == "" {
r, err := p.git.commitTag("HEAD")
if err != nil {
@ -243,6 +229,24 @@ func (p Pkg) Build(bos ...BuildOpt) error {
} else {
fmt.Fprintf(writer, "%s not found\n", ref)
}
if bo.targetDocker {
notFound := false
for _, platform := range bo.platforms {
archRef, err := reference.Parse(fmt.Sprintf("%s-%s", p.FullTag(), platform.Architecture))
if err != nil {
return err
}
fmt.Fprintf(writer, "checking for %s in local cache, fallback to remote registry...\n", archRef)
if _, err := c.ImagePull(&archRef, "", platform.Architecture, false); err == nil {
fmt.Fprintf(writer, "%s found or pulled\n", archRef)
skipBuild = true
} else {
fmt.Fprintf(writer, "%s not found\n", archRef)
notFound = true
}
}
skipBuild = skipBuild && !notFound
}
}
if !skipBuild {
@ -327,14 +331,28 @@ func (p Pkg) Build(bos ...BuildOpt) error {
}
// if requested docker, load the image up
// we will store images with arch suffix, i.e. -amd64
// if one of the arch equals with system, we will add tag without suffix
if bo.targetDocker {
cacheSource := c.NewSource(&ref, arch, desc)
reader, err := cacheSource.V1TarReader()
if err != nil {
return fmt.Errorf("unable to get reader from cache: %v", err)
}
if err := d.load(reader); err != nil {
return err
for _, platform := range bo.platforms {
archRef, err := reference.Parse(fmt.Sprintf("%s-%s", p.FullTag(), platform.Architecture))
if err != nil {
return err
}
cacheSource := c.NewSource(&archRef, platform.Architecture, desc)
reader, err := cacheSource.V1TarReader()
if err != nil {
return fmt.Errorf("unable to get reader from cache: %v", err)
}
if err := d.load(reader); err != nil {
return err
}
if platform.Architecture == arch {
err = d.tag(fmt.Sprintf("%s-%s", p.FullTag(), platform.Architecture), p.FullTag())
if err != nil {
return err
}
}
}
}
@ -375,9 +393,18 @@ func (p Pkg) Build(bos ...BuildOpt) error {
}
// tag in docker, if requested
// will tag all images with arch suffix
// if one of the arch equals with system will add tag without suffix
if bo.targetDocker {
if err := d.tag(p.FullTag(), fullRelTag); err != nil {
return err
for _, platform := range bo.platforms {
if err := d.tag(fmt.Sprintf("%s-%s", p.FullTag(), platform.Architecture), fmt.Sprintf("%s-%s", fullRelTag, platform.Architecture)); err != nil {
return err
}
if platform.Architecture == arch {
if err := d.tag(fmt.Sprintf("%s-%s", p.FullTag(), platform.Architecture), fullRelTag); err != nil {
return err
}
}
}
}

View File

@ -9,7 +9,6 @@ import (
"io"
"io/ioutil"
"math/rand"
"runtime"
"strings"
"testing"
@ -116,7 +115,7 @@ func (c *cacheMocker) ImageLoad(ref *reference.Spec, architecture string, r io.R
}
func (c *cacheMocker) imageWriteStream(ref *reference.Spec, architecture string, r io.Reader) (lktspec.ImageSource, error) {
image := ref.String()
image := fmt.Sprintf("%s-%s", ref.String(), architecture)
// make some random data for a layer
b, err := ioutil.ReadAll(r)
@ -154,10 +153,14 @@ func (c *cacheMocker) imageWriteStream(ref *reference.Spec, architecture string,
Annotations: map[string]string{
imagespec.AnnotationRefName: image,
},
Platform: &registry.Platform{
OS: "linux",
Architecture: architecture,
},
}
c.appendImage(image, desc)
return c.NewSource(ref, "", &desc), nil
return c.NewSource(ref, architecture, &desc), nil
}
func (c *cacheMocker) IndexWrite(ref *reference.Spec, descriptors ...registry.Descriptor) (lktspec.ImageSource, error) {
@ -273,7 +276,13 @@ func (c cacheMockerSource) TarReader() (io.ReadCloser, error) {
return nil, errors.New("unsupported")
}
func (c cacheMockerSource) V1TarReader() (io.ReadCloser, error) {
return nil, errors.New("unsupported")
_, found := c.c.images[c.ref.String()]
if !found {
return nil, fmt.Errorf("no image found with ref: %s", c.ref.String())
}
b := make([]byte, 256)
rand.Read(b)
return io.NopCloser(bytes.NewReader(b)), nil
}
func (c cacheMockerSource) Descriptor() *registry.Descriptor {
return c.descriptor
@ -281,14 +290,8 @@ func (c cacheMockerSource) Descriptor() *registry.Descriptor {
func TestBuild(t *testing.T) {
var (
nonLocal string
cacheDir = "somecachedir"
)
if runtime.GOARCH == "amd64" {
nonLocal = "arm64"
} else {
nonLocal = "amd64"
}
tests := []struct {
msg string
p Pkg
@ -302,7 +305,7 @@ func TestBuild(t *testing.T) {
{"not at head", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64"}, commitHash: "foo"}, nil, []string{"amd64"}, &dockerMocker{supportContexts: false}, &cacheMocker{}, "Cannot build from commit hash != HEAD"},
{"no build cache", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64"}, commitHash: "HEAD"}, nil, []string{"amd64"}, &dockerMocker{supportContexts: false}, &cacheMocker{}, "must provide linuxkit build cache"},
{"unsupported contexts", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64"}, commitHash: "HEAD"}, []BuildOpt{WithBuildCacheDir(cacheDir)}, []string{"amd64"}, &dockerMocker{supportContexts: false}, &cacheMocker{}, "contexts not supported, check docker version"},
{"load docker without local platform", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64", "arm64"}, commitHash: "HEAD"}, []BuildOpt{WithBuildCacheDir(cacheDir), WithBuildTargetDockerCache()}, []string{nonLocal}, &dockerMocker{supportContexts: false}, &cacheMocker{}, "must build for local platform"},
{"load docker without local platform", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64", "arm64"}, commitHash: "HEAD"}, []BuildOpt{WithBuildCacheDir(cacheDir), WithBuildTargetDockerCache()}, []string{"amd64", "arm64"}, &dockerMocker{supportContexts: true, enableBuild: true, images: map[string][]byte{}, enableTag: true}, &cacheMocker{enableImagePull: false, enableImageLoad: true, enableIndexWrite: true}, ""},
{"amd64", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64", "arm64"}, commitHash: "HEAD"}, []BuildOpt{WithBuildCacheDir(cacheDir)}, []string{"amd64"}, &dockerMocker{supportContexts: true, enableBuild: true}, &cacheMocker{enableImagePull: false, enableImageLoad: true, enableIndexWrite: true}, ""},
{"arm64", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64", "arm64"}, commitHash: "HEAD"}, []BuildOpt{WithBuildCacheDir(cacheDir)}, []string{"arm64"}, &dockerMocker{supportContexts: true, enableBuild: true}, &cacheMocker{enableImagePull: false, enableImageLoad: true, enableIndexWrite: true}, ""},
{"amd64 and arm64", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64", "arm64"}, commitHash: "HEAD"}, []BuildOpt{WithBuildCacheDir(cacheDir)}, []string{"amd64", "arm64"}, &dockerMocker{supportContexts: true, enableBuild: true}, &cacheMocker{enableImagePull: false, enableImageLoad: true, enableIndexWrite: true}, ""},

View File

@ -288,7 +288,7 @@ func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, im
}
// create the builder
args := []string{"container", "run", "-d", "--name", name, "--privileged", image, "--allow-insecure-entitlement", "network.host", "--addr", fmt.Sprintf("unix://%s", buildkitSocketPath), "--debug"}
msg := fmt.Sprintf("creating builder container '%s' in context '%s", name, dockerContext)
msg := fmt.Sprintf("creating builder container '%s' in context '%s'", name, dockerContext)
fmt.Println(msg)
if err := dr.command(nil, ioutil.Discard, ioutil.Discard, args...); err != nil {
return nil, err