-
Notifications
You must be signed in to change notification settings - Fork 54
Add push and pull support using cnab-to-oci #681
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"path/filepath" | ||
|
||
"github.com/deislabs/duffle/pkg/duffle/home" | ||
"github.com/deislabs/duffle/pkg/reference" | ||
|
||
"github.com/deislabs/cnab-go/bundle" | ||
"github.com/docker/cnab-to-oci/remotes" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
type pullCmd struct { | ||
output string | ||
targetRef string | ||
insecureRegistries []string | ||
home home.Home | ||
} | ||
|
||
func newPullCmd(w io.Writer) *cobra.Command { | ||
const usage = `Pulls a bundle from an OCI repository` | ||
const pullDesc = ` | ||
Pulls a CNAB bundle from an OCI repository. | ||
The only argument for this command is the repository where | ||
the bundle can be found, and by default, this command pulls the | ||
bundle and stores it in the local bundle store. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this command do anything with images referenced by the bundle? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, by default it does not pull the referenced images. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’m revisiting this PR after the whole OCI discussion - do you think it pulling the bundle should pull the referenced images? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it makes sense for a local development use case to pre-populate the local docker image store (just pull the bundle and everything in it and it "works"), so I agree with the optional flag. By the way I don't think it should be a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I agree - I wasn't thinking this should be in |
||
|
||
If the --output flag is passed, the bundle file will be saved in | ||
that file, and its reference will not be recorded in the local store. | ||
|
||
Insecure registries can be passed through the --insecure-registries flags. | ||
|
||
Examples: | ||
$ duffle pull registry/username/bundle:tag | ||
$ duffle pull --output path-for-bundle.json registry/username/bundle:tag | ||
` | ||
var pull pullCmd | ||
cmd := &cobra.Command{ | ||
Use: "pull [TARGET REPOSITORY] [options]", | ||
Short: usage, | ||
Long: pullDesc, | ||
Args: cobra.ExactArgs(1), | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
pull.targetRef = args[0] | ||
pull.home = home.Home(homePath()) | ||
return pull.run() | ||
}, | ||
} | ||
|
||
cmd.Flags().StringVarP(&pull.output, "output", "o", "", "Output file") | ||
cmd.Flags().StringSliceVar(&pull.insecureRegistries, "insecure-registries", nil, "Use plain HTTP for those registries") | ||
return cmd | ||
} | ||
|
||
func (p *pullCmd) run() error { | ||
ref, err := reference.ParseNormalizedNamed(p.targetRef) | ||
if err != nil { | ||
return err | ||
} | ||
b, err := remotes.Pull(context.Background(), ref, createResolver(p.insecureRegistries)) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return p.writeBundle(b) | ||
} | ||
|
||
func (p *pullCmd) writeBundle(bf *bundle.Bundle) error { | ||
data, digest, err := marshalBundle(bf) | ||
if err != nil { | ||
return fmt.Errorf("cannot marshal bundle: %v", err) | ||
} | ||
|
||
if p.output != "" { | ||
if err := ioutil.WriteFile(p.output, data, 0644); err != nil { | ||
return fmt.Errorf("cannot write bundle to %s: %v", p.output, err) | ||
} | ||
return nil | ||
} | ||
|
||
if err := ioutil.WriteFile(filepath.Join(p.home.Bundles(), digest), data, 0644); err != nil { | ||
return fmt.Errorf("cannot store bundle : %v", err) | ||
|
||
} | ||
|
||
return recordBundleReference(p.home, bf.Name, bf.Version, digest) | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"os" | ||
|
||
"github.com/deislabs/duffle/pkg/duffle/home" | ||
"github.com/deislabs/duffle/pkg/reference" | ||
|
||
containerdRemotes "github.com/containerd/containerd/remotes" | ||
"github.com/docker/cli/cli/config" | ||
"github.com/docker/cnab-to-oci/remotes" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
type pushCmd struct { | ||
inputBundle string | ||
home home.Home | ||
bundleIsFile bool | ||
targetRef string | ||
insecureRegistries []string | ||
allowFallbacks bool | ||
} | ||
|
||
func newPushCmd(out io.Writer) *cobra.Command { | ||
const usage = `Pushes a CNAB bundle to an OCI repository.` | ||
const pushDesc = ` | ||
Pushes a CNAB bundle to an OCI registry by pushing all container | ||
images referenced in the bundle to the target repository (all images are | ||
pushed to the same repository, and are referenceable through their digest). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where are images "pushed" from? Are they copied direct from their remote repositories or do they have to be present in a docker daemon? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, they are directly copied from the remote (and actually, as #734 tracks, if the images are only present in the local daemon, this will fail).. |
||
|
||
The first argument is the bundle to push (or the path to the bundle file, if the | ||
--bundle-is-file flag is passed), and the second argument is the target repository | ||
where the bundle and all referenced images will be pushed. | ||
|
||
Insecure registries can be passed through the --insecure-registries flags, | ||
and --allow-fallbacks enables automatic compatibility fallbacks for registries | ||
without support for custom media type, or OCI manifests. | ||
|
||
Examples: | ||
$ duffle push bundle-reference registry/usernamne/bundle:tag | ||
$ duffle push path-to-bundle.json --bundle-is-file registtry/username/bundle:tag | ||
` | ||
var push pushCmd | ||
|
||
cmd := &cobra.Command{ | ||
Use: "push [BUNDLE] [TARGET REPOSITORY] [options]", | ||
Short: usage, | ||
Long: pushDesc, | ||
Args: cobra.ExactArgs(2), | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
push.home = home.Home(homePath()) | ||
push.inputBundle = args[0] | ||
push.targetRef = args[1] | ||
return push.run() | ||
}, | ||
} | ||
|
||
cmd.Flags().BoolVarP(&push.bundleIsFile, "bundle-is-file", "f", false, "Indicates that the bundle source is a file path") | ||
cmd.Flags().StringSliceVar(&push.insecureRegistries, "insecure-registries", nil, "Use plain HTTP for those registries") | ||
cmd.Flags().BoolVar(&push.allowFallbacks, "allow-fallbacks", true, "Enable automatic compatibility fallbacks for registries without support for custom media type, or OCI manifests") | ||
|
||
return cmd | ||
} | ||
|
||
func (p *pushCmd) run() error { | ||
|
||
bundleFile, err := resolveBundleFilePath(p.inputBundle, p.home.String(), p.bundleIsFile) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
b, err := loadBundle(bundleFile) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
resolver := createResolver(p.insecureRegistries) | ||
ref, err := reference.ParseNormalizedNamed(p.targetRef) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = remotes.FixupBundle(context.Background(), b, ref, resolver, remotes.WithEventCallback(displayEvent)) | ||
if err != nil { | ||
return err | ||
} | ||
d, err := remotes.Push(context.Background(), b, ref, resolver, p.allowFallbacks) | ||
if err != nil { | ||
return err | ||
} | ||
fmt.Printf("Pushed successfully, with digest %q\n", d.Digest) | ||
return nil | ||
} | ||
|
||
func createResolver(insecureRegistries []string) containerdRemotes.Resolver { | ||
return remotes.CreateResolver(config.LoadDefaultConfigFile(os.Stderr), insecureRegistries...) | ||
} | ||
|
||
func displayEvent(ev remotes.FixupEvent) { | ||
switch ev.EventType { | ||
case remotes.FixupEventTypeCopyImageStart: | ||
fmt.Fprintf(os.Stderr, "Starting to copy image %s...\n", ev.SourceImage) | ||
case remotes.FixupEventTypeCopyImageEnd: | ||
if ev.Error != nil { | ||
fmt.Fprintf(os.Stderr, "Failed to copy image %s: %s\n", ev.SourceImage, ev.Error) | ||
} else { | ||
fmt.Fprintf(os.Stderr, "Completed image %s copy\n", ev.SourceImage) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use const block