From 39f1649995ab44ecd05a9c95102a5e4496e38b03 Mon Sep 17 00:00:00 2001 From: Petr Fedchenkov Date: Fri, 15 Jul 2022 15:59:23 +0300 Subject: [PATCH] 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 --- src/cmd/linuxkit/pkglib/build.go | 73 ++++++++++++++++++--------- src/cmd/linuxkit/pkglib/build_test.go | 25 +++++---- src/cmd/linuxkit/pkglib/docker.go | 2 +- 3 files changed, 65 insertions(+), 35 deletions(-) diff --git a/src/cmd/linuxkit/pkglib/build.go b/src/cmd/linuxkit/pkglib/build.go index 18b7ebe00..ff0f52174 100644 --- a/src/cmd/linuxkit/pkglib/build.go +++ b/src/cmd/linuxkit/pkglib/build.go @@ -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 + } + } } } diff --git a/src/cmd/linuxkit/pkglib/build_test.go b/src/cmd/linuxkit/pkglib/build_test.go index eaa5cc714..5aa808718 100644 --- a/src/cmd/linuxkit/pkglib/build_test.go +++ b/src/cmd/linuxkit/pkglib/build_test.go @@ -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: ®istry.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}, ""}, diff --git a/src/cmd/linuxkit/pkglib/docker.go b/src/cmd/linuxkit/pkglib/docker.go index aa827cb49..f3a4a6f57 100644 --- a/src/cmd/linuxkit/pkglib/docker.go +++ b/src/cmd/linuxkit/pkglib/docker.go @@ -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