Skip to content

Commit

Permalink
crane push: Support OCI layout (#1208)
Browse files Browse the repository at this point in the history
* crane push: Support OCI layout

Detect if the first argument is a directory. If so, attempt to read it
as an OCI image layout and push to the destination if it contains a
single image.

* Use os.Stat instead of os.Open

* Add e2e test for pull/push

Make sure we can roundtrip images through an OCI layout.

* Support --index

* ./hack/update-codegen.sh

* Fix e2e

* Address feedback

Use type switch.

Elaborate in description of --index flag.
  • Loading branch information
jonjohnsonjr authored Dec 21, 2021
1 parent d1271fe commit 1d1583a
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 15 deletions.
10 changes: 9 additions & 1 deletion .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
with:
go-version: 1.17.x

- name: crane append to an image, set the entrypoint, run it locally
- name: crane append to an image, set the entrypoint, run it locally, roundtrip it
shell: bash
run: |
set -euxo pipefail
Expand Down Expand Up @@ -55,3 +55,11 @@ jobs:
# Run the image with and without args.
docker run $img
docker run $img --help
# Make sure we can roundtrip it through pull/push
layout=$(mktemp -d)
dst=localhost:1338/roundtrip-test
./app/crane pull --format=oci $img $layout
./app/crane push $layout $dst
diff <(./app/crane manifest $img) <(./app/crane manifest $dst)
2 changes: 1 addition & 1 deletion cmd/crane/cmd/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func NewCmdPull(options *[]crane.Option) *cobra.Command {

cmd := &cobra.Command{
Use: "pull IMAGE TARBALL",
Short: "Pull remote images by reference and store their contents in a tarball",
Short: "Pull remote images by reference and store their contents locally",
Args: cobra.MinimumNArgs(2),
RunE: func(_ *cobra.Command, args []string) error {
imageMap := map[string]v1.Image{}
Expand Down
77 changes: 71 additions & 6 deletions cmd/crane/cmd/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,90 @@ package cmd

import (
"fmt"
"os"

"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/spf13/cobra"
)

// NewCmdPush creates a new cobra.Command for the push subcommand.
func NewCmdPush(options *[]crane.Option) *cobra.Command {
return &cobra.Command{
Use: "push TARBALL IMAGE",
Short: "Push image contents as a tarball to a remote registry",
index := false
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(_ *cobra.Command, args []string) error {
path, tag := args[0], args[1]
img, err := crane.Load(path)

img, err := loadImage(path, index)
if err != nil {
return fmt.Errorf("loading %s as tarball: %w", path, err)
return err
}

// TODO(generics): Make crane.Push support index.
switch t := img.(type) {
case v1.Image:
return crane.Push(t, tag, *options...)
case v1.ImageIndex:
o := crane.GetOptions(*options...)
ref, err := name.ParseReference(tag, o.Name...)
if err != nil {
return err
}
return remote.WriteIndex(ref, t, o.Remote...)
}

return crane.Push(img, tag, *options...)
return fmt.Errorf("cannot push type (%T) to registry", img)
},
}
cmd.Flags().BoolVar(&index, "index", false, "push a collection of images as a single index, currently required if PATH contains multiple images")
return cmd
}

func loadImage(path string, index bool) (partial.WithRawManifest, error) {
stat, err := os.Stat(path)
if err != nil {
return nil, err
}

if !stat.IsDir() {
img, err := crane.Load(path)
if err != nil {
return nil, fmt.Errorf("loading %s as tarball: %w", path, err)
}
return img, nil
}

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

if index {
return l, nil
}

m, err := l.IndexManifest()
if err != nil {
return nil, err
}
if len(m.Manifests) != 1 {
return nil, fmt.Errorf("layout contains %d entries, consider --index", len(m.Manifests))
}

desc := m.Manifests[0]
if desc.MediaType.IsImage() {
return l.Image(desc.Digest)
} else if desc.MediaType.IsIndex() {
return l.ImageIndex(desc.Digest)
}

return nil, fmt.Errorf("layout contains non-image (mediaType: %q), consider --index", desc.MediaType)
}
4 changes: 2 additions & 2 deletions cmd/crane/doc/crane.md

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

2 changes: 1 addition & 1 deletion cmd/crane/doc/crane_pull.md

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

11 changes: 8 additions & 3 deletions cmd/crane/doc/crane_push.md

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

1 change: 0 additions & 1 deletion pkg/v1/zz_deepcopy_generated.go

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

0 comments on commit 1d1583a

Please sign in to comment.