Skip to content

Commit

Permalink
support push bundle
Browse files Browse the repository at this point in the history
Signed-off-by: hunshcn <[email protected]>
  • Loading branch information
hunshcn committed Sep 16, 2023
1 parent a748190 commit 9a8aca1
Show file tree
Hide file tree
Showing 9 changed files with 307 additions and 65 deletions.
4 changes: 2 additions & 2 deletions cmd/crane/cmd/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func NewCmdPull(options *[]crane.Option) *cobra.Command {
return err
}
opts = append(opts, layout.WithAnnotations(map[string]string{
"org.opencontainers.image.ref.name": parsed.Name(),
ociAnnotationImageRefName: parsed.Name(),
}))
}
if err = p.AppendImage(img, opts...); err != nil {
Expand All @@ -117,7 +117,7 @@ func NewCmdPull(options *[]crane.Option) *cobra.Command {
return err
}
opts = append(opts, layout.WithAnnotations(map[string]string{
"org.opencontainers.image.ref.name": parsed.Name(),
ociAnnotationImageRefName: parsed.Name(),
}))
}
if err := p.AppendIndex(idx, opts...); err != nil {
Expand Down
221 changes: 190 additions & 31 deletions cmd/crane/cmd/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,73 +15,136 @@
package cmd

import (
"context"
"fmt"
"os"
"strings"
"sync"

"github.com/google/go-containerregistry/pkg/crane"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/layout"
"github.com/google/go-containerregistry/pkg/v1/partial"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/tarball"
"github.com/spf13/cobra"
)

type imageWithRef struct {
image partial.WithRawManifest
ref name.Reference
}

// NewCmdPush creates a new cobra.Command for the push subcommand.
func NewCmdPush(options *[]crane.Option) *cobra.Command {
index := false
imageRefs := ""
var (
imageRefs string
index, annotateRef bool
)
cmd := &cobra.Command{
Use: "push PATH IMAGE",
Short: "Push local image contents to a remote registry",
Long: `If the PATH is a directory, it will be read as an OCI image layout. Otherwise, PATH is assumed to be a docker-style tarball.`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
path, tag := args[0], args[1]
if !annotateRef {
if err := cobra.ExactArgs(2)(cmd, args); err != nil {
return err
}
path, tag := args[0], args[1]
img, err := loadImage(path, index)
if err != nil {
return err
}

img, err := loadImage(path, index)
if err != nil {
return err
}
o := crane.GetOptions(*options...)
ref, err := name.ParseReference(tag, o.Name...)
if err != nil {
return err
}

o := crane.GetOptions(*options...)
ref, err := name.ParseReference(tag, o.Name...)
if err != nil {
return err
}
var h v1.Hash
switch t := img.(type) {
case v1.Image:
if err := remote.Write(ref, t, o.Remote...); err != nil {
digest, err := writeImage(ref, img, o.Remote...)
if err != nil {
return err
}
if h, err = t.Digest(); err != nil {
if imageRefs != "" {
return os.WriteFile(imageRefs, []byte(digest.String()), 0600)
}

// Print the digest of the pushed image to stdout to facilitate command composition.
fmt.Fprintln(cmd.OutOrStdout(), digest)
} else {
if err := cobra.RangeArgs(1, 2)(cmd, args); err != nil {
return err
}
case v1.ImageIndex:
if err := remote.WriteIndex(ref, t, o.Remote...); err != nil {
path := args[0]
var registry *name.Registry
if len(args) == 2 {
r, err := name.NewRegistry(args[1])
if err != nil {
return err
}
registry = &r
}
imgRefs, err := loadImageWithRef(path, index)
if err != nil {
return err
}
if h, err = t.Digest(); err != nil {
pusher, err := remote.NewPusher()
if err != nil {
return err
}
default:
return fmt.Errorf("cannot push type (%T) to registry", img)
}

digest := ref.Context().Digest(h.String())
if imageRefs != "" {
return os.WriteFile(imageRefs, []byte(digest.String()), 0600)
}
ctx, cancel := context.WithCancel(cmd.Context())
defer cancel()
o := crane.GetOptions(*options...)
o.Remote = append(o.Remote, remote.WithContext(ctx), remote.Reuse[*remote.Pusher](pusher))
wg := sync.WaitGroup{}
var digests []string
for i := range imgRefs {
wg.Add(1)
go func(imgRef imageWithRef) (err error) {
defer func() {
if err != nil {
fmt.Println(err)
cancel()
}
wg.Done()
}()
if registry != nil {
switch t := imgRef.ref.(type) {
case name.Tag:
t.Registry = *registry
imgRef.ref = t
case name.Digest:
t.Registry = *registry
imgRef.ref = t
}
}
digest, err := writeImage(imgRef.ref, imgRef.image, o.Remote...)
if err != nil {
return err
}

// Print the digest of the pushed image to stdout to facilitate command composition.
fmt.Fprintln(cmd.OutOrStdout(), digest)
if imageRefs != "" {
digests = append(digests, digest.String())
}

// Print the digest of the pushed image to stdout to facilitate command composition.
fmt.Fprintln(cmd.OutOrStdout(), digest)
return nil
}(imgRefs[i])
}
wg.Wait()
if imageRefs != "" {
return os.WriteFile(imageRefs, []byte(strings.Join(digests, "\n")), 0600)
}
}
return nil
},
}
cmd.Flags().BoolVar(&index, "index", false, "push a collection of images as a single index, currently required if PATH contains multiple images")
cmd.Flags().StringVar(&imageRefs, "image-refs", "", "path to file where a list of the published image references will be written")
cmd.Flags().BoolVar(&annotateRef, "annotate-ref", false, "use image reference to push bundle")
return cmd
}

Expand Down Expand Up @@ -125,3 +188,99 @@ func loadImage(path string, index bool) (partial.WithRawManifest, error) {

return nil, fmt.Errorf("layout contains non-image (mediaType: %q), consider --index", desc.MediaType)
}

func loadImageWithRef(path string, index bool) ([]imageWithRef, error) {
stat, err := os.Stat(path)
if err != nil {
return nil, err
}

if !stat.IsDir() {
imgs, err := tarball.ImageAllFromPath(path)
if err != nil {
return nil, fmt.Errorf("loading %s as tarball: %w", path, err)
}
var imgRefs []imageWithRef
for _, img := range imgs {
imgTagged, ok := img.(partial.WithRepoTags)
if !ok || len(imgTagged.RepoTags()) == 0 {
return nil, fmt.Errorf("image %s has no tag", path)
}
for _, repoTag := range imgTagged.RepoTags() {
ref, err := name.ParseReference(repoTag, name.StrictValidation)
if err != nil {
return nil, fmt.Errorf("parsing %s repoTag: %w", path, err)
}
imgRefs = append(imgRefs, imageWithRef{img, ref})
}
}
return imgRefs, nil
}

l, err := layout.ImageIndexFromPath(path)
if err != nil {
return nil, fmt.Errorf("loading %s as OCI layout: %w", path, err)
}

m, err := l.IndexManifest()
if err != nil {
return nil, err
}

if index {
refName := m.Annotations[ociAnnotationImageRefName]
ref, err := name.ParseReference(refName, name.StrictValidation)
if err != nil {
return nil, fmt.Errorf("parsing %s repoTag: %w", path, err)
}
return []imageWithRef{{l, ref}}, err
}

imgRefs := make([]imageWithRef, len(m.Manifests))
for i := range m.Manifests {
refName := m.Manifests[i].Annotations[ociAnnotationImageRefName]
ref, err := name.ParseReference(refName, name.StrictValidation)
if err != nil {
return nil, fmt.Errorf("parsing %s repoTag: %w", path, err)
}
if m.Manifests[i].MediaType.IsImage() {
img, err := l.Image(m.Manifests[i].Digest)
if err != nil {
return nil, err
}
imgRefs[i] = imageWithRef{img, ref}
} else if m.Manifests[i].MediaType.IsIndex() {
img, err := l.ImageIndex(m.Manifests[i].Digest)
if err != nil {
return nil, err
}
imgRefs[i] = imageWithRef{img, ref}
} else {
return nil, fmt.Errorf("layout contains unexpected mediaType: %q", m.Manifests[i].MediaType)
}
}
return imgRefs, nil
}

func writeImage(ref name.Reference, img partial.WithRawManifest, options ...remote.Option) (digest name.Digest, err error) {
var h v1.Hash
switch t := img.(type) {
case v1.Image:
if err = remote.Write(ref, t, options...); err != nil {
return
}
if h, err = t.Digest(); err != nil {
return
}
case v1.ImageIndex:
if err = remote.WriteIndex(ref, t, options...); err != nil {
return
}
if h, err = t.Digest(); err != nil {
return
}
default:
return digest, fmt.Errorf("cannot push type (%T) to registry", img)
}
return ref.Context().Digest(h.String()), nil
}
11 changes: 11 additions & 0 deletions cmd/crane/cmd/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@
package cmd

import (
"github.com/google/go-containerregistry/pkg/name"
"strings"

v1 "github.com/google/go-containerregistry/pkg/v1"
)

const ociAnnotationImageRefName = "org.opencontainers.image.ref.name"

type platformsValue struct {
platforms []v1.Platform
}
Expand Down Expand Up @@ -84,3 +87,11 @@ func parsePlatform(platform string) (*v1.Platform, error) {

return v1.ParsePlatform(platform)
}

func referenceMapToStringMap(m map[name.Reference]v1.Image) map[string]v1.Image {
out := make(map[string]v1.Image, len(m))
for k, v := range m {
out[k.String()] = v
}
return out
}
1 change: 1 addition & 0 deletions cmd/crane/doc/crane_push.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 9a8aca1

Please sign in to comment.