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 }