github.com/nikkelma/oras-project_oras-go@v1.1.1-0.20220201001104-a75f6a419090/pkg/oras/copy.go (about)

     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 oras
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"sync"
    22  
    23  	"github.com/containerd/containerd/content"
    24  	"github.com/containerd/containerd/errdefs"
    25  	"github.com/containerd/containerd/images"
    26  	"github.com/containerd/containerd/log"
    27  	"github.com/containerd/containerd/remotes"
    28  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    29  	"oras.land/oras-go/pkg/target"
    30  )
    31  
    32  // Copy copy a ref from one target.Target to a ref in another target.Target. If toRef is blank, reuses fromRef
    33  // Returns the root
    34  // Descriptor of the copied item. Can use the root to retrieve child elements from target.Target.
    35  func Copy(ctx context.Context, from target.Target, fromRef string, to target.Target, toRef string, opts ...CopyOpt) (ocispec.Descriptor, error) {
    36  	if from == nil {
    37  		return ocispec.Descriptor{}, ErrFromTargetUndefined
    38  	}
    39  	if to == nil {
    40  		return ocispec.Descriptor{}, ErrToTargetUndefined
    41  	}
    42  	// blank toRef
    43  	if toRef == "" {
    44  		toRef = fromRef
    45  	}
    46  	opt := copyOptsDefaults()
    47  	for _, o := range opts {
    48  		if err := o(opt); err != nil {
    49  			return ocispec.Descriptor{}, err
    50  		}
    51  	}
    52  
    53  	if from == nil {
    54  		return ocispec.Descriptor{}, ErrFromResolverUndefined
    55  	}
    56  	if to == nil {
    57  		return ocispec.Descriptor{}, ErrToResolverUndefined
    58  	}
    59  
    60  	// for the "from", we resolve the ref, then use resolver.Fetcher to fetch the various content blobs
    61  	// for the "to", we simply use resolver.Pusher to push the various content blobs
    62  
    63  	_, desc, err := from.Resolve(ctx, fromRef)
    64  	if err != nil {
    65  		return ocispec.Descriptor{}, fmt.Errorf("from resolve: %w", err)
    66  	}
    67  
    68  	fetcher, err := from.Fetcher(ctx, fromRef)
    69  	if err != nil {
    70  		return ocispec.Descriptor{}, err
    71  	}
    72  	// construct the reference we send to the pusher using the digest, so it knows what the root is
    73  	pushRef := fmt.Sprintf("%s@%s", toRef, desc.Digest.String())
    74  	pusher, err := to.Pusher(ctx, pushRef)
    75  	if err != nil {
    76  		return ocispec.Descriptor{}, err
    77  	}
    78  
    79  	if err := transferContent(ctx, desc, fetcher, pusher, opt); err != nil {
    80  		return ocispec.Descriptor{}, err
    81  	}
    82  	return desc, nil
    83  }
    84  
    85  func transferContent(ctx context.Context, desc ocispec.Descriptor, fetcher remotes.Fetcher, pusher remotes.Pusher, opts *copyOpts) error {
    86  	var descriptors, manifests []ocispec.Descriptor
    87  	lock := &sync.Mutex{}
    88  	picker := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
    89  		if isAllowedMediaType(desc.MediaType, opts.allowedMediaTypes...) {
    90  			if opts.filterName(desc) {
    91  				lock.Lock()
    92  				defer lock.Unlock()
    93  				descriptors = append(descriptors, desc)
    94  			}
    95  			return nil, nil
    96  		}
    97  		return nil, nil
    98  	})
    99  
   100  	// we use a hybrid store - a cache wrapping the underlying pusher - for two reasons:
   101  	// 1. so that we can cache the manifests as pushing them, then retrieve them later to push in reverse order after the blobs
   102  	// 2. so that we can retrieve them to analyze and find children in the Dispatch routine
   103  	store := opts.contentProvideIngesterPusherFetcher
   104  	if store == nil {
   105  		store = newHybridStoreFromPusher(pusher, opts.cachedMediaTypes, true)
   106  	}
   107  
   108  	// fetchHandler pushes to the *store*, which may or may not cache it
   109  	baseFetchHandler := func(p remotes.Pusher, f remotes.Fetcher) images.HandlerFunc {
   110  		return images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   111  			cw, err := p.Push(ctx, desc)
   112  			if err != nil {
   113  				if !errdefs.IsAlreadyExists(err) {
   114  					return nil, err
   115  				}
   116  				fmt.Printf("ORAS FORK - layer already exists: %s\n", desc.Digest)
   117  				return nil, nil
   118  			}
   119  			defer cw.Close()
   120  
   121  			rc, err := f.Fetch(ctx, desc)
   122  			if err != nil {
   123  				return nil, err
   124  			}
   125  			defer rc.Close()
   126  			return nil, content.Copy(ctx, cw, rc, desc.Size, desc.Digest)
   127  		})
   128  	}
   129  
   130  	// track all of our manifests that will be cached
   131  	fetchHandler := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   132  		if isAllowedMediaType(desc.MediaType, opts.cachedMediaTypes...) {
   133  			lock.Lock()
   134  			defer lock.Unlock()
   135  			manifests = append(manifests, desc)
   136  		}
   137  		return baseFetchHandler(store, fetcher)(ctx, desc)
   138  	})
   139  
   140  	handlers := []images.Handler{
   141  		filterHandler(opts, opts.allowedMediaTypes...),
   142  	}
   143  	handlers = append(handlers, opts.baseHandlers...)
   144  	handlers = append(handlers,
   145  		fetchHandler,
   146  		picker,
   147  		images.ChildrenHandler(&ProviderWrapper{Fetcher: store}),
   148  	)
   149  	handlers = append(handlers, opts.callbackHandlers...)
   150  
   151  	if err := opts.dispatch(ctx, images.Handlers(handlers...), nil, desc); err != nil {
   152  		return err
   153  	}
   154  
   155  	// we cached all of the manifests, so push those out
   156  	// Iterate in reverse order as seen, parent always uploaded after child
   157  	for i := len(manifests) - 1; i >= 0; i-- {
   158  		_, err := baseFetchHandler(pusher, store)(ctx, manifests[i])
   159  		if err != nil {
   160  			return err
   161  		}
   162  	}
   163  
   164  	// if the option to request the root manifest was passed, accommodate it
   165  	if opts.saveManifest != nil && len(manifests) > 0 {
   166  		rc, err := store.Fetch(ctx, manifests[0])
   167  		if err != nil {
   168  			return fmt.Errorf("could not get root manifest to save based on CopyOpt: %v", err)
   169  		}
   170  		defer rc.Close()
   171  		buf := new(bytes.Buffer)
   172  		if _, err := buf.ReadFrom(rc); err != nil {
   173  			return fmt.Errorf("unable to read data for root manifest to save based on CopyOpt: %v", err)
   174  		}
   175  		// get the root manifest from the store
   176  		opts.saveManifest(buf.Bytes())
   177  	}
   178  
   179  	// if the option to request the layers was passed, accommodate it
   180  	if opts.saveLayers != nil && len(descriptors) > 0 {
   181  		opts.saveLayers(descriptors)
   182  	}
   183  	return nil
   184  }
   185  
   186  func filterHandler(opts *copyOpts, allowedMediaTypes ...string) images.HandlerFunc {
   187  	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   188  		switch {
   189  		case isAllowedMediaType(desc.MediaType, ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex):
   190  			return nil, nil
   191  		case isAllowedMediaType(desc.MediaType, allowedMediaTypes...):
   192  			if opts.filterName(desc) {
   193  				return nil, nil
   194  			}
   195  			log.G(ctx).Warnf("blob no name: %v", desc.Digest)
   196  		default:
   197  			log.G(ctx).Warnf("unknown type: %v", desc.MediaType)
   198  		}
   199  		return nil, images.ErrStopHandler
   200  	}
   201  }
   202  
   203  func isAllowedMediaType(mediaType string, allowedMediaTypes ...string) bool {
   204  	if len(allowedMediaTypes) == 0 {
   205  		return true
   206  	}
   207  	for _, allowedMediaType := range allowedMediaTypes {
   208  		if mediaType == allowedMediaType {
   209  			return true
   210  		}
   211  	}
   212  	return false
   213  }