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") 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 == "" { if p.git != nil && bo.push && bo.release == "" {
r, err := p.git.commitTag("HEAD") r, err := p.git.commitTag("HEAD")
if err != nil { if err != nil {
@ -243,6 +229,24 @@ func (p Pkg) Build(bos ...BuildOpt) error {
} else { } else {
fmt.Fprintf(writer, "%s not found\n", ref) 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 { if !skipBuild {
@ -327,14 +331,28 @@ func (p Pkg) Build(bos ...BuildOpt) error {
} }
// if requested docker, load the image up // 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 { if bo.targetDocker {
cacheSource := c.NewSource(&ref, arch, desc) for _, platform := range bo.platforms {
reader, err := cacheSource.V1TarReader() archRef, err := reference.Parse(fmt.Sprintf("%s-%s", p.FullTag(), platform.Architecture))
if err != nil { if err != nil {
return fmt.Errorf("unable to get reader from cache: %v", err) return err
} }
if err := d.load(reader); err != nil { cacheSource := c.NewSource(&archRef, platform.Architecture, desc)
return err 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 // 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 bo.targetDocker {
if err := d.tag(p.FullTag(), fullRelTag); err != nil { for _, platform := range bo.platforms {
return err 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"
"io/ioutil" "io/ioutil"
"math/rand" "math/rand"
"runtime"
"strings" "strings"
"testing" "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) { 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 // make some random data for a layer
b, err := ioutil.ReadAll(r) b, err := ioutil.ReadAll(r)
@ -154,10 +153,14 @@ func (c *cacheMocker) imageWriteStream(ref *reference.Spec, architecture string,
Annotations: map[string]string{ Annotations: map[string]string{
imagespec.AnnotationRefName: image, imagespec.AnnotationRefName: image,
}, },
Platform: &registry.Platform{
OS: "linux",
Architecture: architecture,
},
} }
c.appendImage(image, desc) 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) { 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") return nil, errors.New("unsupported")
} }
func (c cacheMockerSource) V1TarReader() (io.ReadCloser, error) { 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 { func (c cacheMockerSource) Descriptor() *registry.Descriptor {
return c.descriptor return c.descriptor
@ -281,14 +290,8 @@ func (c cacheMockerSource) Descriptor() *registry.Descriptor {
func TestBuild(t *testing.T) { func TestBuild(t *testing.T) {
var ( var (
nonLocal string
cacheDir = "somecachedir" cacheDir = "somecachedir"
) )
if runtime.GOARCH == "amd64" {
nonLocal = "arm64"
} else {
nonLocal = "amd64"
}
tests := []struct { tests := []struct {
msg string msg string
p Pkg 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"}, {"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"}, {"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"}, {"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}, ""}, {"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}, ""}, {"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}, ""}, {"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 // create the builder
args := []string{"container", "run", "-d", "--name", name, "--privileged", image, "--allow-insecure-entitlement", "network.host", "--addr", fmt.Sprintf("unix://%s", buildkitSocketPath), "--debug"} 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) fmt.Println(msg)
if err := dr.command(nil, ioutil.Discard, ioutil.Discard, args...); err != nil { if err := dr.command(nil, ioutil.Discard, ioutil.Discard, args...); err != nil {
return nil, err return nil, err