Skip to content
This repository was archived by the owner on May 3, 2022. It is now read-only.

Commit 61bb2d7

Browse files
author
Radu M
committed
Add initial push-pull implementation
Signed-off-by: Radu M <[email protected]>
1 parent 367ce6b commit 61bb2d7

File tree

15 files changed

+2227
-0
lines changed

15 files changed

+2227
-0
lines changed

Gopkg.lock

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,8 @@
101101
# version imported by k8s.io/client-go @ kubernetes-1.11.2
102102
name = "github.com/Azure/go-autorest"
103103
revision = "1ff28809256a84bb6966640ff3d0371af82ccba4"
104+
105+
[[constraint]]
106+
name = "github.com/docker/cnab-to-oci"
107+
source = "github.com/radu-matei/cnab-to-oci"
108+
branch = "sync-bundle"

cmd/duffle/pull.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"io/ioutil"
8+
"path/filepath"
9+
10+
"github.com/deislabs/duffle/pkg/duffle/home"
11+
"github.com/deislabs/duffle/pkg/reference"
12+
13+
"github.com/deislabs/cnab-go/bundle"
14+
"github.com/docker/cnab-to-oci/remotes"
15+
"github.com/spf13/cobra"
16+
)
17+
18+
type pullCmd struct {
19+
output string
20+
targetRef string
21+
insecureRegistries []string
22+
home home.Home
23+
}
24+
25+
func newPullCmd(w io.Writer) *cobra.Command {
26+
const usage = `Pulls a bundle from an OCI repository`
27+
const pullDesc = `
28+
Pulls a CNAB bundle from an OCI repository.
29+
The only argument for this command is the repository where
30+
the bundle can be found, and by default, this command pulls the
31+
bundle and stores it in the local bundle store.
32+
33+
If the --output flag is passed, the bundle file will be saved in
34+
that file, and its reference will not be recorded in the local store.
35+
36+
Insecure registries can be passed through the --insecure-registries flags.
37+
38+
Examples:
39+
$ duffle pull registry/username/bundle:tag
40+
$ duffle pull --output path-for-bundle.json registry/username/bundle:tag
41+
`
42+
var pull pullCmd
43+
cmd := &cobra.Command{
44+
Use: "pull [TARGET REPOSITORY] [options]",
45+
Short: usage,
46+
Long: pullDesc,
47+
Args: cobra.ExactArgs(1),
48+
RunE: func(cmd *cobra.Command, args []string) error {
49+
pull.targetRef = args[0]
50+
pull.home = home.Home(homePath())
51+
return pull.run()
52+
},
53+
}
54+
55+
cmd.Flags().StringVarP(&pull.output, "output", "o", "", "Output file")
56+
cmd.Flags().StringSliceVar(&pull.insecureRegistries, "insecure-registries", nil, "Use plain HTTP for those registries")
57+
return cmd
58+
}
59+
60+
func (p *pullCmd) run() error {
61+
ref, err := reference.ParseNormalizedNamed(p.targetRef)
62+
if err != nil {
63+
return err
64+
}
65+
b, err := remotes.Pull(context.Background(), ref, createResolver(p.insecureRegistries))
66+
if err != nil {
67+
return err
68+
}
69+
70+
return p.writeBundle(b)
71+
}
72+
73+
func (p *pullCmd) writeBundle(bf *bundle.Bundle) error {
74+
data, digest, err := marshalBundle(bf)
75+
if err != nil {
76+
return fmt.Errorf("cannot marshal bundle: %v", err)
77+
}
78+
79+
if p.output != "" {
80+
if err := ioutil.WriteFile(p.output, data, 0644); err != nil {
81+
return fmt.Errorf("cannot write bundle to %s: %v", p.output, err)
82+
}
83+
return nil
84+
}
85+
86+
if err := ioutil.WriteFile(filepath.Join(p.home.Bundles(), digest), data, 0644); err != nil {
87+
return fmt.Errorf("cannot store bundle : %v", err)
88+
89+
}
90+
91+
return recordBundleReference(p.home, bf.Name, bf.Version, digest)
92+
93+
}

cmd/duffle/push.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"os"
8+
9+
"github.com/deislabs/duffle/pkg/duffle/home"
10+
"github.com/deislabs/duffle/pkg/reference"
11+
12+
containerdRemotes "github.com/containerd/containerd/remotes"
13+
"github.com/docker/cli/cli/config"
14+
"github.com/docker/cnab-to-oci/remotes"
15+
"github.com/spf13/cobra"
16+
)
17+
18+
type pushCmd struct {
19+
inputBundle string
20+
home home.Home
21+
bundleIsFile bool
22+
targetRef string
23+
insecureRegistries []string
24+
allowFallbacks bool
25+
}
26+
27+
func newPushCmd(out io.Writer) *cobra.Command {
28+
const usage = `Pushes a CNAB bundle to an OCI repository.`
29+
const pushDesc = `
30+
Pushes a CNAB bundle to an OCI registry by pushing all container
31+
images referenced in the bundle to the target repository (all images are
32+
pushed to the same repository, and are referenceable through their digest).
33+
34+
The first argument is the bundle to push (or the path to the bundle file, if the
35+
--bundle-is-file flag is passed), and the second argument is the target repository
36+
where the bundle and all referenced images will be pushed.
37+
38+
Insecure registries can be passed through the --insecure-registries flags,
39+
and --allow-fallbacks enables automatic compatibility fallbacks for registries
40+
without support for custom media type, or OCI manifests.
41+
42+
Examples:
43+
$ duffle push bundle-reference registry/usernamne/bundle:tag
44+
$ duffle push path-to-bundle.json --bundle-is-file registtry/username/bundle:tag
45+
`
46+
var push pushCmd
47+
48+
cmd := &cobra.Command{
49+
Use: "push [BUNDLE] [TARGET REPOSITORY] [options]",
50+
Short: usage,
51+
Long: pushDesc,
52+
Args: cobra.ExactArgs(2),
53+
RunE: func(cmd *cobra.Command, args []string) error {
54+
push.home = home.Home(homePath())
55+
push.inputBundle = args[0]
56+
push.targetRef = args[1]
57+
return push.run()
58+
},
59+
}
60+
61+
cmd.Flags().BoolVarP(&push.bundleIsFile, "bundle-is-file", "f", false, "Indicates that the bundle source is a file path")
62+
cmd.Flags().StringSliceVar(&push.insecureRegistries, "insecure-registries", nil, "Use plain HTTP for those registries")
63+
cmd.Flags().BoolVar(&push.allowFallbacks, "allow-fallbacks", true, "Enable automatic compatibility fallbacks for registries without support for custom media type, or OCI manifests")
64+
65+
return cmd
66+
}
67+
68+
func (p *pushCmd) run() error {
69+
70+
bundleFile, err := resolveBundleFilePath(p.inputBundle, p.home.String(), p.bundleIsFile)
71+
if err != nil {
72+
return err
73+
}
74+
75+
b, err := loadBundle(bundleFile)
76+
if err != nil {
77+
return err
78+
}
79+
80+
resolver := createResolver(p.insecureRegistries)
81+
ref, err := reference.ParseNormalizedNamed(p.targetRef)
82+
if err != nil {
83+
return err
84+
}
85+
86+
err = remotes.FixupBundle(context.Background(), b, ref, resolver, remotes.WithEventCallback(displayEvent))
87+
if err != nil {
88+
return err
89+
}
90+
d, err := remotes.Push(context.Background(), b, ref, resolver, p.allowFallbacks)
91+
if err != nil {
92+
return err
93+
}
94+
fmt.Printf("Pushed successfully, with digest %q\n", d.Digest)
95+
return nil
96+
}
97+
98+
func createResolver(insecureRegistries []string) containerdRemotes.Resolver {
99+
return remotes.CreateResolver(config.LoadDefaultConfigFile(os.Stderr), insecureRegistries...)
100+
}
101+
102+
func displayEvent(ev remotes.FixupEvent) {
103+
switch ev.EventType {
104+
case remotes.FixupEventTypeCopyImageStart:
105+
fmt.Fprintf(os.Stderr, "Starting to copy image %s...\n", ev.SourceImage)
106+
case remotes.FixupEventTypeCopyImageEnd:
107+
if ev.Error != nil {
108+
fmt.Fprintf(os.Stderr, "Failed to copy image %s: %s\n", ev.SourceImage, ev.Error)
109+
} else {
110+
fmt.Fprintf(os.Stderr, "Completed image %s copy\n", ev.SourceImage)
111+
}
112+
}
113+
}

cmd/duffle/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ func newRootCmd(outputRedirect io.Writer) *cobra.Command {
5959
newExportCmd(outLog),
6060
newImportCmd(outLog),
6161
newCreateCmd(outLog),
62+
newPullCmd(outLog),
63+
newPushCmd(outLog),
6264
)
6365

6466
return cmd

0 commit comments

Comments
 (0)