Skip to content

Commit

Permalink
Copy API
Browse files Browse the repository at this point in the history
Signed-off-by: Avi Deitcher <[email protected]>
  • Loading branch information
deitch committed Sep 2, 2021
1 parent 2802bf6 commit 4cf3563
Show file tree
Hide file tree
Showing 32 changed files with 1,557 additions and 1,040 deletions.
182 changes: 182 additions & 0 deletions examples/advanced/advanced.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package main

import (
"context"
"fmt"
"os"
"path/filepath"
"strings"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"oras.land/oras-go/pkg/content"
"oras.land/oras-go/pkg/oras"
"oras.land/oras-go/pkg/target"
)

func main() {
var verbose int
cmd := &cobra.Command{
Use: fmt.Sprintf("%s [command]", os.Args[0]),
SilenceUsage: true,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
log.SetLevel(log.InfoLevel)
if verbose > 1 {
log.SetLevel(log.DebugLevel)
}
},
}
cmd.AddCommand(copyCmd())
cmd.PersistentFlags().IntVarP(&verbose, "verbose", "v", 1, "set log level")
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
}

func copyCmd() *cobra.Command {
var (
fromStr, toStr string
manifestConfig string
opts content.RegistryOptions
)
cmd := &cobra.Command{
Use: "copy <name:tag|name@digest>",
Short: "Copy artifacts from one location to another",
Long: `Copy artifacts from one location to another
Example - Copy artifacts from local files to local files:
oras copy foo/bar:v1 --from files --to files:path/to/save file1 file2 ... filen
Example - Copy artifacts from registry to local files:
oras copy foo/bar:v1 --from registry --to files:path/to/save
Example - Copy artifacts from registry to oci:
oras copy foo/bar:v1 --from registry --to oci:path/to/oci
Example - Copy artifacts from local files to registry:
oras copy foo/bar:v1 --from files --to registry file1 file2 ... filen
When the source (--from) is "files", the config by default will be "{}" and of media type
application/vnd.unknown.config.v1+json. You can override it by setting the path, for example:
oras copy foo/bar:v1 --from files --manifest-config path/to/config:application/vnd.oci.image.config.v1+json --to files:path/to/save file1 file2 ... filen
`,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var (
ref = args[0]
err error
from, to target.Target
)
// get the fromStr; it might also have a ':' to add options
fromParts := strings.SplitN(fromStr, ":", 2)
toParts := strings.SplitN(toStr, ":", 2)
switch fromParts[0] {
case "files":
fromFile := content.NewFile("")
descs, err := loadFiles(fromFile, args[1:]...)
if err != nil {
return fmt.Errorf("unable to load files: %v", err)
}
// parse the manifest config
manifestConfigParts := strings.SplitN(manifestConfig, ":", 2)
var manifestConfigPath, manifestConfigMediaType string
switch len(manifestConfigParts) {
case 1:
manifestConfigPath = manifestConfigParts[0]
case 2:
manifestConfigPath = manifestConfigParts[0]
manifestConfigMediaType = manifestConfigParts[1]
}
if err != nil {
return fmt.Errorf("error reading manifest config at %s: %v", manifestConfigPath, err)
}
configDesc, err := fromFile.Add("", manifestConfigMediaType, manifestConfigPath)
if err != nil {
return fmt.Errorf("unable to load manifest config: %v", err)
}
if _, err := fromFile.GenerateManifest(ref, &configDesc, descs...); err != nil {
return fmt.Errorf("unable to generate root manifest: %s", err)
}
rootDesc, rootManifest, err := fromFile.Ref(ref)
if err != nil {
return err
}
log.Debugf("root manifest: %s %v %s", ref, rootDesc, rootManifest)
from = fromFile
case "registry":
from, err = content.NewRegistry(opts)
if err != nil {
return fmt.Errorf("could not create registry target: %v", err)
}
case "oci":
from, err = content.NewOCI(fromParts[1])
if err != nil {
return fmt.Errorf("could not read OCI layout at %s: %v", fromParts[1], err)
}
default:
return fmt.Errorf("unknown from argyment: %s", from)
}

switch toParts[0] {
case "files":
to = content.NewFile(toParts[1])
case "registry":
to, err = content.NewRegistry(opts)
if err != nil {
return fmt.Errorf("could not create registry target: %v", err)
}
case "oci":
to, err = content.NewOCI(toParts[1])
if err != nil {
return fmt.Errorf("could not read OCI layout at %s: %v", toParts[1], err)
}
default:
return fmt.Errorf("unknown from argyment: %s", from)
}

if manifestConfig != "" && fromParts[0] != "files" {
return fmt.Errorf("only specify --manifest-config when using --from files")
}
return runCopy(ref, from, to)
},
}
cmd.Flags().StringVar(&fromStr, "from", "", "source type and possible options")
cmd.MarkFlagRequired("from")
cmd.Flags().StringVar(&toStr, "to", "", "destination type and possible options")
cmd.MarkFlagRequired("to")
cmd.Flags().StringArrayVarP(&opts.Configs, "config", "c", nil, "auth config path")
cmd.Flags().StringVarP(&opts.Username, "username", "u", "", "registry username")
cmd.Flags().StringVarP(&opts.Password, "password", "p", "", "registry password")
cmd.Flags().BoolVarP(&opts.Insecure, "insecure", "", false, "allow connections to SSL registry without certs")
cmd.Flags().BoolVarP(&opts.PlainHTTP, "plain-http", "", false, "use plain http and not https")
cmd.Flags().StringVar(&manifestConfig, "manifest-config", "", "path to manifest config and its media type, e.g. path/to/file.json:application/vnd.oci.image.config.v1+json")
return cmd
}

func runCopy(ref string, from, to target.Target) error {
desc, err := oras.Copy(context.Background(), from, ref, to, "")
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v", err)
os.Exit(1)
}
fmt.Printf("%#v\n", desc)
return nil
}

func loadFiles(store *content.File, files ...string) ([]ocispec.Descriptor, error) {
var descs []ocispec.Descriptor
for _, fileRef := range files {
filename, mediaType := parseFileRef(fileRef, "")
name := filepath.Clean(filename)
if !filepath.IsAbs(name) {
// convert to slash-separated path unless it is absolute path
name = filepath.ToSlash(name)
}
desc, err := store.Add(name, mediaType, filename)
if err != nil {
return nil, err
}
descs = append(descs, desc)
}
return descs, nil
}
13 changes: 13 additions & 0 deletions examples/advanced/file_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// +build !windows

package main

import "strings"

func parseFileRef(ref string, mediaType string) (string, string) {
i := strings.LastIndex(ref, ":")
if i < 0 {
return ref, mediaType
}
return ref[:i], ref[i+1:]
}
26 changes: 26 additions & 0 deletions examples/advanced/file_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package main

import (
"strings"
"unicode"
)

// parseFileRef parse file reference on windows.
// Windows systems does not allow ':' in the file path except for drive letter.
func parseFileRef(ref string, mediaType string) (string, string) {
i := strings.Index(ref, ":")
if i < 0 {
return ref, mediaType
}

// In case it is C:\
if i == 1 && len(ref) > 2 && ref[2] == '\\' && unicode.IsLetter(rune(ref[0])) {
i = strings.Index(ref[3:], ":")
if i < 0 {
return ref, mediaType
}
i += 3
}

return ref[:i], ref[i+1:]
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ import (
"fmt"
"os"

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

"oras.land/oras-go/pkg/content"
"oras.land/oras-go/pkg/oras"
)
Expand All @@ -48,23 +45,25 @@ func main() {
customMediaType := "my.custom.media.type"

ctx := context.Background()
resolver := docker.NewResolver(docker.ResolverOptions{PlainHTTP: true})

// Push file(s) w custom mediatype to registry
memoryStore := content.NewMemoryStore()
desc := memoryStore.Add(fileName, customMediaType, fileContent)
pushContents := []ocispec.Descriptor{desc}
memoryStore := content.NewMemory()
desc, err := memoryStore.Add(fileName, customMediaType, fileContent)
check(err)
_, err = memoryStore.GenerateManifest(ref, nil, desc)
check(err)
registry, err := content.NewRegistry(content.RegistryOptions{PlainHTTP: true})
fmt.Printf("Pushing %s to %s...\n", fileName, ref)
desc, err := oras.Push(ctx, resolver, ref, memoryStore, pushContents)
desc, err = oras.Copy(ctx, memoryStore, ref, registry, "")
check(err)
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...\n", ref, fileName)
fileStore := content.NewFileStore("")
fileStore := content.NewFile("")
defer fileStore.Close()
allowedMediaTypes := []string{customMediaType}
desc, _, err = oras.Pull(ctx, resolver, ref, fileStore, oras.WithAllowedMediaTypes(allowedMediaTypes))
desc, err = oras.Copy(ctx, registry, ref, fileStore, "", oras.WithAllowedMediaTypes(allowedMediaTypes))
check(err)
fmt.Printf("Pulled from %s with digest %s\n", ref, desc.Digest)
fmt.Printf("Try running 'cat %s'\n", fileName)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.0.0 // indirect
github.com/stretchr/testify v1.7.0
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
Expand Down
Loading

0 comments on commit 4cf3563

Please sign in to comment.