get.porter.sh/porter@v1.3.0/pkg/porter/copy.go (about) 1 package porter 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "strings" 8 9 "get.porter.sh/porter/pkg/cnab" 10 cnabtooci "get.porter.sh/porter/pkg/cnab/cnab-to-oci" 11 "get.porter.sh/porter/pkg/config" 12 "get.porter.sh/porter/pkg/tracing" 13 "go.opentelemetry.io/otel/attribute" 14 ) 15 16 type CopyOpts struct { 17 Source string 18 sourceRef cnab.OCIReference 19 Destination string 20 InsecureRegistry bool 21 Force bool 22 SignBundle bool 23 PreserveTags bool 24 } 25 26 // Validate performs validation logic on the options specified for a bundle copy 27 func (c *CopyOpts) Validate(cfg *config.Config) error { 28 var err error 29 if c.Destination == "" { 30 return errors.New("--destination is required") 31 } 32 33 c.sourceRef, err = cnab.ParseOCIReference(c.Source) 34 if err != nil { 35 return fmt.Errorf("invalid value for --source, specified value should be of the form REGISTRY/bundle:tag or REGISTRY/bundle@sha: %w", err) 36 } 37 if c.sourceRef.HasDigest() && isCopyReferenceOnly(c.Destination) { 38 return errors.New("--destination must be tagged reference when --source is digested reference") 39 } 40 41 // Apply the global config for force overwrite 42 if !c.Force && cfg.Data.ForceOverwrite { 43 c.Force = true 44 } 45 46 return nil 47 } 48 49 func isCopyReferenceOnly(dest string) bool { 50 ref, err := cnab.ParseOCIReference(dest) 51 if err != nil { 52 return false 53 } 54 return ref.IsRepositoryOnly() 55 } 56 57 func generateNewBundleRef(source cnab.OCIReference, dest string) (cnab.OCIReference, error) { 58 if isCopyReferenceOnly(dest) { 59 srcVal := source.String() 60 bundleNameRef := srcVal[strings.LastIndex(srcVal, "/")+1:] 61 dest = fmt.Sprintf("%s/%s", dest, bundleNameRef) 62 } 63 return cnab.ParseOCIReference(dest) 64 } 65 66 // CopyBundle copies a bundle from one repository to another 67 func (p *Porter) CopyBundle(ctx context.Context, opts *CopyOpts) error { 68 ctx, span := tracing.StartSpan(ctx, 69 attribute.String("source", opts.sourceRef.String()), 70 attribute.String("destination", opts.Destination), 71 ) 72 defer span.EndSpan() 73 74 destinationRef, err := generateNewBundleRef(opts.sourceRef, opts.Destination) 75 if err != nil { 76 return span.Error(err) 77 } 78 79 regOpts := cnabtooci.RegistryOptions{ 80 InsecureRegistry: opts.InsecureRegistry, 81 } 82 83 // Before we attempt to push, check if it already exists in the destination registry 84 if !opts.Force { 85 _, err := p.Registry.GetBundleMetadata(ctx, destinationRef, regOpts) 86 if err != nil { 87 if !errors.Is(err, cnabtooci.ErrNotFound{}) { 88 return span.Errorf("Copy stopped because detection of %s in the destination registry failed. To overwrite it, repeat the command with --force specified: %w", destinationRef, err) 89 } 90 } else { 91 return span.Errorf("Copy stopped because %s already exists in the destination registry. To overwrite it, repeat the command with --force specified.", destinationRef) 92 } 93 } 94 95 span.Infof("Beginning bundle copy to %s. This may take some time.", destinationRef) 96 bunRef, err := p.Registry.PullBundle(ctx, opts.sourceRef, regOpts) 97 if err != nil { 98 return span.Error(fmt.Errorf("unable to pull bundle before copying: %w", err)) 99 } 100 101 bunRef.Reference = destinationRef 102 bunRef, err = p.Registry.PushBundle(ctx, bunRef, regOpts) 103 if err != nil { 104 return span.Error(fmt.Errorf("unable to copy bundle to new location: %w", err)) 105 } 106 107 if opts.SignBundle { 108 for _, invImage := range bunRef.Definition.InvocationImages { 109 relocInvImage := bunRef.RelocationMap[invImage.Image] 110 span.Debugf("Signing bundle image %s...", relocInvImage) 111 err = p.Signer.Sign(ctx, relocInvImage) 112 if err != nil { 113 return span.Errorf("failed to sign image %s: %w", relocInvImage, err) 114 } 115 } 116 117 span.Debugf("Signing bundle %s", bunRef.Reference.String()) 118 err = p.Signer.Sign(ctx, bunRef.Reference.String()) 119 if err != nil { 120 return span.Errorf("failed to bundle %s: %w", bunRef.Reference.String(), err) 121 } 122 } 123 124 return nil 125 }