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  }