Skip to content

Commit

Permalink
Push/Pull return the descriptor of the associated tag (#70)
Browse files Browse the repository at this point in the history
return ref manifest
  • Loading branch information
shizhMSFT committed Apr 17, 2019
1 parent bf2ff5d commit 550ed34
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 43 deletions.
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,11 @@ import (
"context"
"fmt"

"github.com/containerd/containerd/remotes/docker"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/deislabs/oras/pkg/content"
"github.com/deislabs/oras/pkg/oras"

"github.com/containerd/containerd/remotes/docker"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)

func check(e error) {
Expand All @@ -215,18 +216,19 @@ func main() {
memoryStore := content.NewMemoryStore()
desc := memoryStore.Add(fileName, customMediaType, fileContent)
pushContents := []ocispec.Descriptor{desc}
fmt.Printf("Pushing %s to %s... ", fileName, ref)
err := oras.Push(ctx, resolver, ref, memoryStore, pushContents)
fmt.Printf("Pushing %s to %s...\n", fileName, ref)
desc, err := oras.Push(ctx, resolver, ref, memoryStore, pushContents)
check(err)
fmt.Println("success!")
fmt.Printf("Pushed to %s with digest %s\n", ref, desc.Digest)

// Pull file(s) from registry and save to disk
fmt.Printf("Pulling from %s and saving to %s... ", ref, fileName)
fmt.Printf("Pulling from %s and saving to %s...\n", ref, fileName)
fileStore := content.NewFileStore("")
defer fileStore.Close()
allowedMediaTypes := []string{customMediaType}
_, err = oras.Pull(ctx, resolver, ref, fileStore, allowedMediaTypes...)
desc, _, err = oras.Pull(ctx, resolver, ref, fileStore, oras.WithAllowedMediaTypes(allowedMediaTypes))
check(err)
fmt.Println("success!")
fmt.Printf("Pulled from %s with digest %s\n", ref, desc.Digest)
fmt.Printf("Try running 'cat %s'\n", fileName)
}
```
4 changes: 3 additions & 1 deletion cmd/oras/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func runPull(opts pullOptions) error {
defer store.Close()
store.DisableOverwrite = opts.keepOldFiles
store.AllowPathTraversalOnWrite = opts.pathTraversal
files, err := oras.Pull(context.Background(), resolver, opts.targetRef, store, opts.allowedMediaTypes...)
desc, files, err := oras.Pull(context.Background(), resolver, opts.targetRef, store, oras.WithAllowedMediaTypes(opts.allowedMediaTypes))
if err != nil {
return err
}
Expand All @@ -89,6 +89,8 @@ func runPull(opts pullOptions) error {
fmt.Println(name)
}
}
fmt.Println("Pulled", opts.targetRef)
fmt.Println(desc.Digest)
}

return nil
Expand Down
17 changes: 16 additions & 1 deletion cmd/oras/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"

Expand All @@ -25,6 +26,7 @@ type pushOptions struct {
manifestConfigRef string
manifestAnnotations string
pathValidationDisabled bool
verbose bool

debug bool
configs []string
Expand Down Expand Up @@ -62,6 +64,7 @@ Example - Push file "hi.txt" with the custom manifest config "config.json" of th
cmd.Flags().StringVarP(&opts.manifestConfigRef, "manifest-config", "", "", "manifest config file")
cmd.Flags().StringVarP(&opts.manifestAnnotations, "manifest-annotations", "", "", "manifest annotation file")
cmd.Flags().BoolVarP(&opts.pathValidationDisabled, "disable-path-validation", "", false, "skip path validation")
cmd.Flags().BoolVarP(&opts.verbose, "verbose", "v", false, "verbose output")
cmd.Flags().BoolVarP(&opts.debug, "debug", "d", false, "debug mode")
cmd.Flags().StringArrayVarP(&opts.configs, "config", "c", nil, "auth config path")
cmd.Flags().StringVarP(&opts.username, "username", "u", "", "registry username")
Expand Down Expand Up @@ -112,6 +115,9 @@ func runPush(opts pushOptions) error {
// convert to slash-separated path unless it is absolute path
name = filepath.ToSlash(name)
}
if opts.verbose {
fmt.Println(name)
}
file, err := store.Add(name, mediaType, filename)
if err != nil {
return err
Expand All @@ -132,7 +138,16 @@ func runPush(opts pushOptions) error {

// ready to push
resolver := newResolver(opts.username, opts.password, opts.configs...)
return oras.Push(context.Background(), resolver, opts.targetRef, store, files, pushOpts...)
desc, err := oras.Push(context.Background(), resolver, opts.targetRef, store, files, pushOpts...)
if err != nil {
return err
}

if opts.verbose {
fmt.Println("Pushed", opts.targetRef)
fmt.Println(desc.Digest)
}
return nil
}

func decodeJSON(filename string, v interface{}) error {
Expand Down
18 changes: 10 additions & 8 deletions examples/simple_push_pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import (
"context"
"fmt"

"github.com/containerd/containerd/remotes/docker"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/deislabs/oras/pkg/content"
"github.com/deislabs/oras/pkg/oras"

"github.com/containerd/containerd/remotes/docker"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)

func check(e error) {
Expand All @@ -29,17 +30,18 @@ func main() {
memoryStore := content.NewMemoryStore()
desc := memoryStore.Add(fileName, customMediaType, fileContent)
pushContents := []ocispec.Descriptor{desc}
fmt.Printf("Pushing %s to %s... ", fileName, ref)
err := oras.Push(ctx, resolver, ref, memoryStore, pushContents)
fmt.Printf("Pushing %s to %s...\n", fileName, ref)
desc, err := oras.Push(ctx, resolver, ref, memoryStore, pushContents)
check(err)
fmt.Println("success!")
fmt.Printf("Pushed to %s with digest %s\n", ref, desc.Digest)

// Pull file(s) from registry and save to disk
fmt.Printf("Pulling from %s and saving to %s... ", ref, fileName)
fmt.Printf("Pulling from %s and saving to %s...\n", ref, fileName)
fileStore := content.NewFileStore("")
defer fileStore.Close()
allowedMediaTypes := []string{customMediaType}
_, err = oras.Pull(ctx, resolver, ref, fileStore, allowedMediaTypes...)
desc, _, err = oras.Pull(ctx, resolver, ref, fileStore, oras.WithAllowedMediaTypes(allowedMediaTypes))
check(err)
fmt.Println("success!")
fmt.Printf("Pulled from %s with digest %s\n", ref, desc.Digest)
fmt.Printf("Try running 'cat %s'\n", fileName)
}
79 changes: 69 additions & 10 deletions pkg/oras/oras_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import (
"testing"
"time"

orascontent "github.com/deislabs/oras/pkg/content"

"github.com/containerd/containerd/remotes"
"github.com/containerd/containerd/remotes/docker"
"github.com/docker/distribution/configuration"
"github.com/docker/distribution/registry"
_ "github.com/docker/distribution/registry/storage/driver/inmemory"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/phayes/freeport"
orascontent "github.com/deislabs/oras/pkg/content"
"github.com/stretchr/testify/suite"
)

Expand Down Expand Up @@ -74,14 +75,14 @@ func (suite *ORASTestSuite) Test_0_Push() {
store *orascontent.FileStore
)

err = Push(newContext(), nil, ref, nil, descriptors)
_, err = Push(newContext(), nil, ref, nil, descriptors)
suite.NotNil(err, "error pushing with empty resolver")

err = Push(newContext(), newResolver(), ref, nil, descriptors)
_, err = Push(newContext(), newResolver(), ref, nil, descriptors)
suite.NotNil(err, "error pushing when context missing hostname")

ref = fmt.Sprintf("%s/empty:test", suite.DockerRegistryHost)
err = Push(newContext(), newResolver(), ref, nil, descriptors)
_, err = Push(newContext(), newResolver(), ref, nil, descriptors)
suite.NotNil(ErrEmptyDescriptors, err, "error pushing with empty descriptors")

// Load descriptors with test chart tgz (as single layer)
Expand All @@ -92,7 +93,7 @@ func (suite *ORASTestSuite) Test_0_Push() {
descriptors = []ocispec.Descriptor{desc}

ref = fmt.Sprintf("%s/chart-tgz:test", suite.DockerRegistryHost)
err = Push(newContext(), newResolver(), ref, store, descriptors)
_, err = Push(newContext(), newResolver(), ref, store, descriptors)
suite.Nil(err, "no error pushing test chart tgz (as single layer)")

// Load descriptors with test chart dir (each file as layer)
Expand All @@ -119,7 +120,7 @@ func (suite *ORASTestSuite) Test_0_Push() {
os.Chdir(cwd)

ref = fmt.Sprintf("%s/chart-dir:test", suite.DockerRegistryHost)
err = Push(newContext(), newResolver(), ref, store, descriptors)
_, err = Push(newContext(), newResolver(), ref, store, descriptors)
suite.Nil(err, "no error pushing test chart dir (each file as layer)")
}

Expand All @@ -132,21 +133,21 @@ func (suite *ORASTestSuite) Test_1_Pull() {
store *orascontent.Memorystore
)

descriptors, err = Pull(newContext(), nil, ref, nil)
_, descriptors, err = Pull(newContext(), nil, ref, nil)
suite.NotNil(err, "error pulling with empty resolver")
suite.Nil(descriptors, "descriptors nil pulling with empty resolver")

// Pull non-existant
store = orascontent.NewMemoryStore()
ref = fmt.Sprintf("%s/nonexistant:test", suite.DockerRegistryHost)
descriptors, err = Pull(newContext(), newResolver(), ref, store)
_, descriptors, err = Pull(newContext(), newResolver(), ref, store)
suite.NotNil(err, "error pulling non-existant ref")
suite.Nil(descriptors, "descriptors empty with error")

// Pull chart-tgz
store = orascontent.NewMemoryStore()
ref = fmt.Sprintf("%s/chart-tgz:test", suite.DockerRegistryHost)
descriptors, err = Pull(newContext(), newResolver(), ref, store)
_, descriptors, err = Pull(newContext(), newResolver(), ref, store)
suite.Nil(err, "no error pulling chart-tgz ref")

// Verify the descriptors, single layer/file
Expand All @@ -160,7 +161,7 @@ func (suite *ORASTestSuite) Test_1_Pull() {
// Pull chart-dir
store = orascontent.NewMemoryStore()
ref = fmt.Sprintf("%s/chart-dir:test", suite.DockerRegistryHost)
descriptors, err = Pull(newContext(), newResolver(), ref, store)
_, descriptors, err = Pull(newContext(), newResolver(), ref, store)
suite.Nil(err, "no error pulling chart-dir ref")

// Verify the descriptors, multiple layers/files
Expand All @@ -176,6 +177,64 @@ func (suite *ORASTestSuite) Test_1_Pull() {
os.Chdir(cwd)
}

// Push and pull with customized media types
func (suite *ORASTestSuite) Test_2_MediaType() {
var (
testData = [][]string{
{"hi.txt", "application/vnd.me.hi", "hi"},
{"bye.txt", "application/vnd.me.bye", "bye"},
}
err error
ref string
descriptors []ocispec.Descriptor
store *orascontent.Memorystore
)

// Push content with customized media types
store = orascontent.NewMemoryStore()
descriptors = nil
for _, data := range testData {
desc := store.Add(data[0], data[1], []byte(data[2]))
descriptors = append(descriptors, desc)
}
ref = fmt.Sprintf("%s/media-type:test", suite.DockerRegistryHost)
_, err = Push(newContext(), newResolver(), ref, store, descriptors)
suite.Nil(err, "no error pushing test data with customized media type")

// Pull with all media types
store = orascontent.NewMemoryStore()
ref = fmt.Sprintf("%s/media-type:test", suite.DockerRegistryHost)
_, descriptors, err = Pull(newContext(), newResolver(), ref, store)
suite.Nil(err, "no error pulling media-type ref")
suite.Equal(2, len(descriptors), "number of contents matches on pull")
for _, data := range testData {
_, actualContent, ok := store.GetByName(data[0])
suite.True(ok, "find in memory")
content := []byte(data[2])
suite.Equal(content, actualContent, "test content matches on pull")
}

// Pull with specified media type
store = orascontent.NewMemoryStore()
ref = fmt.Sprintf("%s/media-type:test", suite.DockerRegistryHost)
_, descriptors, err = Pull(newContext(), newResolver(), ref, store, WithAllowedMediaType(testData[0][1]))
suite.Nil(err, "no error pulling media-type ref")
suite.Equal(1, len(descriptors), "number of contents matches on pull")
for _, data := range testData[:1] {
_, actualContent, ok := store.GetByName(data[0])
suite.True(ok, "find in memory")
content := []byte(data[2])
suite.Equal(content, actualContent, "test content matches on pull")
}

// Pull with non-existing media type
store = orascontent.NewMemoryStore()
ref = fmt.Sprintf("%s/media-type:test", suite.DockerRegistryHost)
_, descriptors, err = Pull(newContext(), newResolver(), ref, store, WithAllowedMediaType("non.existing.media.type"))
suite.Nil(err, "no error pulling media-type ref")
suite.Equal(0, len(descriptors), "number of contents matches on pull")
}

func TestORASTestSuite(t *testing.T) {
suite.Run(t, new(ORASTestSuite))
}
23 changes: 17 additions & 6 deletions pkg/oras/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,42 @@ import (
"context"
"sync"

orascontent "github.com/deislabs/oras/pkg/content"

"github.com/containerd/containerd/content"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/remotes"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
orascontent "github.com/deislabs/oras/pkg/content"
)

// Pull pull files from the remote
func Pull(ctx context.Context, resolver remotes.Resolver, ref string, ingester content.Ingester, allowedMediaTypes ...string) ([]ocispec.Descriptor, error) {
func Pull(ctx context.Context, resolver remotes.Resolver, ref string, ingester content.Ingester, opts ...PullOpt) (ocispec.Descriptor, []ocispec.Descriptor, error) {
if resolver == nil {
return nil, ErrResolverUndefined
return ocispec.Descriptor{}, nil, ErrResolverUndefined
}
opt := pullOptsDefaults()
for _, o := range opts {
if err := o(opt); err != nil {
return ocispec.Descriptor{}, nil, err
}
}

_, desc, err := resolver.Resolve(ctx, ref)
if err != nil {
return nil, err
return ocispec.Descriptor{}, nil, err
}

fetcher, err := resolver.Fetcher(ctx, ref)
if err != nil {
return nil, err
return ocispec.Descriptor{}, nil, err
}

return fetchContent(ctx, fetcher, desc, ingester, allowedMediaTypes...)
layers, err := fetchContent(ctx, fetcher, desc, ingester, opt.allowedMediaTypes...)
if err != nil {
return ocispec.Descriptor{}, nil, err
}
return desc, layers, nil
}

func fetchContent(ctx context.Context, fetcher remotes.Fetcher, desc ocispec.Descriptor, ingester content.Ingester, allowedMediaTypes ...string) ([]ocispec.Descriptor, error) {
Expand Down
28 changes: 28 additions & 0 deletions pkg/oras/pull_opts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package oras

type pullOpts struct {
allowedMediaTypes []string
}

// PullOpt allows callers to set options on the oras pull
type PullOpt func(o *pullOpts) error

func pullOptsDefaults() *pullOpts {
return &pullOpts{}
}

// WithAllowedMediaType sets the allowed media types
func WithAllowedMediaType(allowedMediaTypes ...string) PullOpt {
return func(o *pullOpts) error {
o.allowedMediaTypes = append(o.allowedMediaTypes, allowedMediaTypes...)
return nil
}
}

// WithAllowedMediaTypes sets the allowed media types
func WithAllowedMediaTypes(allowedMediaTypes []string) PullOpt {
return func(o *pullOpts) error {
o.allowedMediaTypes = append(o.allowedMediaTypes, allowedMediaTypes...)
return nil
}
}

0 comments on commit 550ed34

Please sign in to comment.