...

Source file src/oras.land/oras-go/examples/advanced/advanced.go

Documentation: oras.land/oras-go/examples/advanced

     1  /*
     2  Copyright The ORAS Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  package main
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"os"
    21  	"path/filepath"
    22  	"strings"
    23  
    24  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    25  	log "github.com/sirupsen/logrus"
    26  	"github.com/spf13/cobra"
    27  
    28  	"oras.land/oras-go/pkg/artifact"
    29  	"oras.land/oras-go/pkg/content"
    30  	"oras.land/oras-go/pkg/oras"
    31  	"oras.land/oras-go/pkg/target"
    32  )
    33  
    34  func main() {
    35  	var verbose int
    36  	cmd := &cobra.Command{
    37  		Use:          fmt.Sprintf("%s [command]", os.Args[0]),
    38  		SilenceUsage: true,
    39  		PersistentPreRun: func(cmd *cobra.Command, args []string) {
    40  			log.SetLevel(log.InfoLevel)
    41  			if verbose > 1 {
    42  				log.SetLevel(log.DebugLevel)
    43  			}
    44  		},
    45  	}
    46  	cmd.AddCommand(copyCmd())
    47  	cmd.PersistentFlags().IntVarP(&verbose, "verbose", "v", 1, "set log level")
    48  	if err := cmd.Execute(); err != nil {
    49  		os.Exit(1)
    50  	}
    51  }
    52  
    53  func copyCmd() *cobra.Command {
    54  	var (
    55  		fromStr, toStr               string
    56  		manifestConfig               string
    57  		manifestAnnotations          map[string]string
    58  		configAnnotations            map[string]string
    59  		showRootManifest, showLayers bool
    60  		opts                         content.RegistryOptions
    61  	)
    62  	cmd := &cobra.Command{
    63  		Use:   "copy <name:tag|name@digest>",
    64  		Short: "Copy artifacts from one location to another",
    65  		Long: `Copy artifacts from one location to another
    66  Example - Copy artifacts from local files to local files:
    67    oras copy foo/bar:v1 --from files --to files:path/to/save file1 file2 ... filen
    68  Example - Copy artifacts from registry to local files:
    69    oras copy foo/bar:v1 --from registry --to files:path/to/save
    70  Example - Copy artifacts from registry to oci:
    71    oras copy foo/bar:v1 --from registry --to oci:path/to/oci
    72  Example - Copy artifacts from local files to registry:
    73    oras copy foo/bar:v1 --from files --to registry file1 file2 ... filen
    74  
    75  When the source (--from) is "files", the config by default will be "{}" and of media type
    76  application/vnd.unknown.config.v1+json. You can override it by setting the path, for example:
    77  
    78    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
    79  
    80  
    81  `,
    82  		Args: cobra.MinimumNArgs(1),
    83  		RunE: func(cmd *cobra.Command, args []string) error {
    84  			var (
    85  				ref        = args[0]
    86  				err        error
    87  				from, to   target.Target
    88  				configDesc ocispec.Descriptor
    89  			)
    90  			// get the fromStr; it might also have a ':' to add options
    91  			fromParts := strings.SplitN(fromStr, ":", 2)
    92  			toParts := strings.SplitN(toStr, ":", 2)
    93  			switch fromParts[0] {
    94  			case "files":
    95  				fromFile := content.NewFile("")
    96  				descs, err := loadFiles(fromFile, args[1:]...)
    97  				if err != nil {
    98  					return fmt.Errorf("unable to load files: %w", err)
    99  				}
   100  				// parse the manifest config
   101  				if manifestConfig != "" {
   102  					manifestConfigPath, manifestConfigMediaType := parseFileRef(manifestConfig, artifact.UnknownConfigMediaType)
   103  					configDesc, err = fromFile.Add("", manifestConfigMediaType, manifestConfigPath)
   104  					if err != nil {
   105  						return fmt.Errorf("unable to load manifest config: %w", err)
   106  					}
   107  				} else {
   108  					var config []byte
   109  					config, configDesc, err = content.GenerateConfig(configAnnotations)
   110  					if err != nil {
   111  						return fmt.Errorf("unable to create new manifest config: %w", err)
   112  					}
   113  					if err := fromFile.Load(configDesc, config); err != nil {
   114  						return fmt.Errorf("unable to load new manifest config: %w", err)
   115  					}
   116  				}
   117  				manifest, manifestDesc, err := content.GenerateManifest(&configDesc, manifestAnnotations, descs...)
   118  				if err != nil {
   119  					return fmt.Errorf("unable to create manifest: %w", err)
   120  				}
   121  				if err := fromFile.StoreManifest(ref, manifestDesc, manifest); err != nil {
   122  					return fmt.Errorf("unable to generate root manifest: %w", err)
   123  				}
   124  				rootDesc, rootManifest, err := fromFile.Ref(ref)
   125  				if err != nil {
   126  					return err
   127  				}
   128  				log.Debugf("root manifest: %s %v %s", ref, rootDesc, rootManifest)
   129  				from = fromFile
   130  			case "registry":
   131  				from, err = content.NewRegistry(opts)
   132  				if err != nil {
   133  					return fmt.Errorf("could not create registry target: %w", err)
   134  				}
   135  			case "oci":
   136  				from, err = content.NewOCI(fromParts[1])
   137  				if err != nil {
   138  					return fmt.Errorf("could not read OCI layout at %s: %w", fromParts[1], err)
   139  				}
   140  			default:
   141  				return fmt.Errorf("unknown from argyment: %s", from)
   142  			}
   143  
   144  			switch toParts[0] {
   145  			case "files":
   146  				to = content.NewFile(toParts[1])
   147  			case "registry":
   148  				to, err = content.NewRegistry(opts)
   149  				if err != nil {
   150  					return fmt.Errorf("could not create registry target: %w", err)
   151  				}
   152  			case "oci":
   153  				to, err = content.NewOCI(toParts[1])
   154  				if err != nil {
   155  					return fmt.Errorf("could not read OCI layout at %s: %v", toParts[1], err)
   156  				}
   157  			default:
   158  				return fmt.Errorf("unknown from argyment: %s", from)
   159  			}
   160  
   161  			if manifestConfig != "" && fromParts[0] != "files" {
   162  				return fmt.Errorf("only specify --manifest-config when using --from files")
   163  			}
   164  			var copyOpts []oras.CopyOpt
   165  			if showRootManifest {
   166  				copyOpts = append(copyOpts, oras.WithRootManifest(func(b []byte) {
   167  					fmt.Printf("root: %s\n", b)
   168  				}))
   169  			}
   170  			if showLayers {
   171  				copyOpts = append(copyOpts, oras.WithLayerDescriptors(func(layers []ocispec.Descriptor) {
   172  					fmt.Printf("%#v\n", layers)
   173  				}))
   174  			}
   175  			return runCopy(ref, from, to, copyOpts...)
   176  		},
   177  	}
   178  	cmd.Flags().StringVar(&fromStr, "from", "", "source type and possible options")
   179  	cmd.MarkFlagRequired("from")
   180  	cmd.Flags().StringVar(&toStr, "to", "", "destination type and possible options")
   181  	cmd.MarkFlagRequired("to")
   182  	cmd.Flags().StringArrayVarP(&opts.Configs, "config", "c", nil, "auth config path")
   183  	cmd.Flags().StringVarP(&opts.Username, "username", "u", "", "registry username")
   184  	cmd.Flags().StringVarP(&opts.Password, "password", "p", "", "registry password")
   185  	cmd.Flags().BoolVarP(&opts.Insecure, "insecure", "", false, "allow connections to SSL registry without certs")
   186  	cmd.Flags().BoolVarP(&opts.PlainHTTP, "plain-http", "", false, "use plain http and not https")
   187  	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")
   188  	cmd.Flags().StringToStringVar(&manifestAnnotations, "manifest-annotations", nil, "key-value pairs of annotations to set on the manifest, e.g. 'annotation=foo,other=bar'")
   189  	cmd.Flags().StringToStringVar(&configAnnotations, "config-annotations", nil, "key-value pairs of annotations to set on the config, only if config is not passed explicitly, e.g. 'annotation=foo,other=bar'")
   190  	cmd.Flags().BoolVarP(&showRootManifest, "show-manifest", "", false, "when copying, show the root manifest")
   191  	cmd.Flags().BoolVarP(&showLayers, "show-layers", "", false, "when copying, show the descriptors for the layers")
   192  	return cmd
   193  }
   194  
   195  func runCopy(ref string, from, to target.Target, copyOpts ...oras.CopyOpt) error {
   196  	desc, err := oras.Copy(context.Background(), from, ref, to, "", copyOpts...)
   197  	if err != nil {
   198  		fmt.Fprintf(os.Stderr, "error: %v", err)
   199  		os.Exit(1)
   200  	}
   201  	fmt.Printf("%#v\n", desc)
   202  	return nil
   203  }
   204  
   205  func loadFiles(store *content.File, files ...string) ([]ocispec.Descriptor, error) {
   206  	var descs []ocispec.Descriptor
   207  	for _, fileRef := range files {
   208  		filename, mediaType := parseFileRef(fileRef, "")
   209  		name := filepath.Clean(filename)
   210  		if !filepath.IsAbs(name) {
   211  			// convert to slash-separated path unless it is absolute path
   212  			name = filepath.ToSlash(name)
   213  		}
   214  		desc, err := store.Add(name, mediaType, filename)
   215  		if err != nil {
   216  			return nil, err
   217  		}
   218  		descs = append(descs, desc)
   219  	}
   220  	return descs, nil
   221  }
   222  

View as plain text