From df36b7aa7d77f383575260cedc9fb6ef418e93d2 Mon Sep 17 00:00:00 2001 From: Avi Deitcher Date: Mon, 7 Jun 2021 11:59:27 +0300 Subject: [PATCH] Push arch-specific tags, always build index from registry Signed-off-by: Avi Deitcher --- src/cmd/linuxkit/cache/push.go | 102 ++++++++++++-------------- src/cmd/linuxkit/registry/manifest.go | 14 +++- 2 files changed, 56 insertions(+), 60 deletions(-) diff --git a/src/cmd/linuxkit/cache/push.go b/src/cmd/linuxkit/cache/push.go index d6de530a8..cf9c44598 100644 --- a/src/cmd/linuxkit/cache/push.go +++ b/src/cmd/linuxkit/cache/push.go @@ -7,65 +7,9 @@ import ( namepkg "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/linuxkit/linuxkit/src/cmd/linuxkit/registry" + log "github.com/sirupsen/logrus" ) -// PushWithManifest push an image along with, optionally, a multi-arch index. -func (p *Provider) PushWithManifest(name, suffix string, pushImage, pushManifest bool) error { - var ( - err error - options []remote.Option - ) - imageName := name + suffix - ref, err := namepkg.ParseReference(imageName) - if err != nil { - return err - } - - if pushImage { - fmt.Printf("Pushing %s\n", imageName) - // do we even have the given one? - root, err := p.FindRoot(imageName) - if err != nil { - return err - } - options = append(options, remote.WithAuthFromKeychain(authn.DefaultKeychain)) - img, err1 := root.Image() - ii, err2 := root.ImageIndex() - switch { - case err1 == nil: - if err := remote.Write(ref, img, options...); err != nil { - return err - } - fmt.Printf("Pushed image %s\n", imageName) - case err2 == nil: - if err := remote.WriteIndex(ref, ii, options...); err != nil { - return err - } - fmt.Printf("Pushed index %s\n", imageName) - default: - return fmt.Errorf("name %s unknown in cache", imageName) - } - } else { - fmt.Print("Image push disabled, skipping...\n") - } - - auth, err := registry.GetDockerAuth() - if err != nil { - return fmt.Errorf("failed to get auth: %v", err) - } - - if pushManifest { - fmt.Printf("Pushing %s to manifest %s\n", imageName, name) - _, _, err = registry.PushManifest(name, auth) - if err != nil { - return err - } - } else { - fmt.Print("Manifest push disabled, skipping...\n") - } - return nil -} - // Push push an image along with a multi-arch index. func (p *Provider) Push(name string) error { var ( @@ -88,17 +32,61 @@ func (p *Provider) Push(name string) error { ii, err2 := root.ImageIndex() switch { case err1 == nil: + log.Debugf("pushing image %s", name) if err := remote.Write(ref, img, options...); err != nil { return err } fmt.Printf("Pushed image %s\n", name) case err2 == nil: + log.Debugf("pushing index %s", name) + // this is an index, so we not only want to write the index, but tags for each arch-specific image in it if err := remote.WriteIndex(ref, ii, options...); err != nil { return err } fmt.Printf("Pushed index %s\n", name) + manifest, err := ii.IndexManifest() + if err != nil { + return fmt.Errorf("successfully pushed index, but could not read images in index: %v", err) + } + log.Debugf("pushing individual images in the index %s", name) + for _, m := range manifest.Manifests { + if m.Platform == nil || m.Platform.Architecture == "" { + continue + } + archTag := fmt.Sprintf("%s-%s", name, m.Platform.Architecture) + tag, err := namepkg.NewTag(archTag) + if err != nil { + return fmt.Errorf("could not create a valid arch-specific tag %s: %v", archTag, err) + } + image, err := p.FindRoot(archTag) + if err != nil { + return fmt.Errorf("could not find arch-specific image in cache %s: %v", archTag, err) + } + img, err := image.Image() + if err != nil { + return fmt.Errorf("found arch-specific image in cache %s, but could not resolve to actual image: %v", archTag, err) + } + log.Debugf("pushing image %s", tag) + if err := remote.Tag(tag, img, options...); err != nil { + return fmt.Errorf("error creating tag %s: %v", archTag, err) + } + } default: return fmt.Errorf("name %s unknown in cache", name) } + + // Even though we may have pushed the index, we want to be sure that we have an index that includes every architecture on the registry, + // not just those that were in our local cache. So we use manifest-tool library to build a broad index + auth, err := registry.GetDockerAuth() + if err != nil { + return fmt.Errorf("failed to get auth: %v", err) + } + + fmt.Printf("Pushing index based on all arch-specific images in registry %s\n", name) + _, _, err = registry.PushManifest(name, auth) + if err != nil { + return err + } + return nil } diff --git a/src/cmd/linuxkit/registry/manifest.go b/src/cmd/linuxkit/registry/manifest.go index 86aea8183..f722984df 100644 --- a/src/cmd/linuxkit/registry/manifest.go +++ b/src/cmd/linuxkit/registry/manifest.go @@ -11,15 +11,23 @@ import ( log "github.com/sirupsen/logrus" ) -var platforms = []string{ - "linux/amd64", "linux/arm64", "linux/s390x", +// these platforms are used only as the source for registry.PushManifestList(). Since it is +// configured to ignore missing, we could include dozens if we wanted; it doesn't hurt, adding maybe a few seconds to +// the whole run. +// Ideally, we could just look for all tags that start with linuxkit/foo:-*, but the registry API +// only supports "list all the tags" and "get a specific tag", no "get by pattern". The "get a specific tag" +// is exactly what registry.PushManifestList() uses, so no benefit to use doing that in advance, +// while "list all tags" is slow, and has to cycle through all of the (growing numbers of) tags +// before we know what exists. Might as well leave it as is. +var platformsToSearchForIndex = []string{ + "linux/amd64", "linux/arm64", "linux/s390x", "linux/riscv64", "linux/ppc64le", } // PushManifest create a manifest that supports each of the provided platforms and push it out. func PushManifest(img string, auth dockertypes.AuthConfig) (hash string, length int, err error) { srcImages := []types.ManifestEntry{} - for i, platform := range platforms { + for i, platform := range platformsToSearchForIndex { osArchArr := strings.Split(platform, "/") if len(osArchArr) != 2 && len(osArchArr) != 3 { return hash, length, fmt.Errorf("platform argument %d is not of form 'os/arch': '%s'", i, platform)