github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/images/handlers.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package images 18 19 import ( 20 "context" 21 "fmt" 22 "sort" 23 24 "github.com/containerd/containerd/content" 25 "github.com/containerd/containerd/errdefs" 26 "github.com/containerd/containerd/platforms" 27 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 28 "github.com/pkg/errors" 29 "golang.org/x/sync/errgroup" 30 "golang.org/x/sync/semaphore" 31 ) 32 33 var ( 34 // ErrSkipDesc is used to skip processing of a descriptor and 35 // its descendants. 36 ErrSkipDesc = fmt.Errorf("skip descriptor") 37 38 // ErrStopHandler is used to signify that the descriptor 39 // has been handled and should not be handled further. 40 // This applies only to a single descriptor in a handler 41 // chain and does not apply to descendant descriptors. 42 ErrStopHandler = fmt.Errorf("stop handler") 43 ) 44 45 // Handler handles image manifests 46 type Handler interface { 47 Handle(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) 48 } 49 50 // HandlerFunc function implementing the Handler interface 51 type HandlerFunc func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) 52 53 // Handle image manifests 54 func (fn HandlerFunc) Handle(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) { 55 return fn(ctx, desc) 56 } 57 58 // Handlers returns a handler that will run the handlers in sequence. 59 // 60 // A handler may return `ErrStopHandler` to stop calling additional handlers 61 func Handlers(handlers ...Handler) HandlerFunc { 62 return func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) { 63 var children []ocispec.Descriptor 64 for _, handler := range handlers { 65 ch, err := handler.Handle(ctx, desc) 66 if err != nil { 67 if errors.Is(err, ErrStopHandler) { 68 break 69 } 70 return nil, err 71 } 72 73 children = append(children, ch...) 74 } 75 76 return children, nil 77 } 78 } 79 80 // Walk the resources of an image and call the handler for each. If the handler 81 // decodes the sub-resources for each image, 82 // 83 // This differs from dispatch in that each sibling resource is considered 84 // synchronously. 85 func Walk(ctx context.Context, handler Handler, descs ...ocispec.Descriptor) error { 86 for _, desc := range descs { 87 88 children, err := handler.Handle(ctx, desc) 89 if err != nil { 90 if errors.Is(err, ErrSkipDesc) { 91 continue // don't traverse the children. 92 } 93 return err 94 } 95 96 if len(children) > 0 { 97 if err := Walk(ctx, handler, children...); err != nil { 98 return err 99 } 100 } 101 } 102 103 return nil 104 } 105 106 // Dispatch runs the provided handler for content specified by the descriptors. 107 // If the handler decode subresources, they will be visited, as well. 108 // 109 // Handlers for siblings are run in parallel on the provided descriptors. A 110 // handler may return `ErrSkipDesc` to signal to the dispatcher to not traverse 111 // any children. 112 // 113 // A concurrency limiter can be passed in to limit the number of concurrent 114 // handlers running. When limiter is nil, there is no limit. 115 // 116 // Typically, this function will be used with `FetchHandler`, often composed 117 // with other handlers. 118 // 119 // If any handler returns an error, the dispatch session will be canceled. 120 func Dispatch(ctx context.Context, handler Handler, limiter *semaphore.Weighted, descs ...ocispec.Descriptor) error { 121 eg, ctx2 := errgroup.WithContext(ctx) 122 for _, desc := range descs { 123 desc := desc 124 125 if limiter != nil { 126 if err := limiter.Acquire(ctx, 1); err != nil { 127 return err 128 } 129 } 130 131 eg.Go(func() error { 132 desc := desc 133 134 children, err := handler.Handle(ctx2, desc) 135 if limiter != nil { 136 limiter.Release(1) 137 } 138 if err != nil { 139 if errors.Is(err, ErrSkipDesc) { 140 return nil // don't traverse the children. 141 } 142 return err 143 } 144 145 if len(children) > 0 { 146 return Dispatch(ctx2, handler, limiter, children...) 147 } 148 149 return nil 150 }) 151 } 152 153 return eg.Wait() 154 } 155 156 // ChildrenHandler decodes well-known manifest types and returns their children. 157 // 158 // This is useful for supporting recursive fetch and other use cases where you 159 // want to do a full walk of resources. 160 // 161 // One can also replace this with another implementation to allow descending of 162 // arbitrary types. 163 func ChildrenHandler(provider content.Provider) HandlerFunc { 164 return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 165 return Children(ctx, provider, desc) 166 } 167 } 168 169 // SetChildrenLabels is a handler wrapper which sets labels for the content on 170 // the children returned by the handler and passes through the children. 171 // Must follow a handler that returns the children to be labeled. 172 func SetChildrenLabels(manager content.Manager, f HandlerFunc) HandlerFunc { 173 return SetChildrenMappedLabels(manager, f, nil) 174 } 175 176 // SetChildrenMappedLabels is a handler wrapper which sets labels for the content on 177 // the children returned by the handler and passes through the children. 178 // Must follow a handler that returns the children to be labeled. 179 // The label map allows the caller to control the labels per child descriptor. 180 // For returned labels, the index of the child will be appended to the end 181 // except for the first index when the returned label does not end with '.'. 182 func SetChildrenMappedLabels(manager content.Manager, f HandlerFunc, labelMap func(ocispec.Descriptor) []string) HandlerFunc { 183 if labelMap == nil { 184 labelMap = ChildGCLabels 185 } 186 return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 187 children, err := f(ctx, desc) 188 if err != nil { 189 return children, err 190 } 191 192 if len(children) > 0 { 193 var ( 194 info = content.Info{ 195 Digest: desc.Digest, 196 Labels: map[string]string{}, 197 } 198 fields = []string{} 199 keys = map[string]uint{} 200 ) 201 for _, ch := range children { 202 labelKeys := labelMap(ch) 203 for _, key := range labelKeys { 204 idx := keys[key] 205 keys[key] = idx + 1 206 if idx > 0 || key[len(key)-1] == '.' { 207 key = fmt.Sprintf("%s%d", key, idx) 208 } 209 210 info.Labels[key] = ch.Digest.String() 211 fields = append(fields, "labels."+key) 212 } 213 } 214 215 _, err := manager.Update(ctx, info, fields...) 216 if err != nil { 217 return nil, err 218 } 219 } 220 221 return children, err 222 } 223 } 224 225 // FilterPlatforms is a handler wrapper which limits the descriptors returned 226 // based on matching the specified platform matcher. 227 func FilterPlatforms(f HandlerFunc, m platforms.Matcher) HandlerFunc { 228 return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 229 children, err := f(ctx, desc) 230 if err != nil { 231 return children, err 232 } 233 234 var descs []ocispec.Descriptor 235 236 if m == nil { 237 descs = children 238 } else { 239 for _, d := range children { 240 if d.Platform == nil || m.Match(*d.Platform) { 241 descs = append(descs, d) 242 } 243 } 244 } 245 246 return descs, nil 247 } 248 } 249 250 // LimitManifests is a handler wrapper which filters the manifest descriptors 251 // returned using the provided platform. 252 // The results will be ordered according to the comparison operator and 253 // use the ordering in the manifests for equal matches. 254 // A limit of 0 or less is considered no limit. 255 // A not found error is returned if no manifest is matched. 256 func LimitManifests(f HandlerFunc, m platforms.MatchComparer, n int) HandlerFunc { 257 return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 258 children, err := f(ctx, desc) 259 if err != nil { 260 return children, err 261 } 262 263 switch desc.MediaType { 264 case ocispec.MediaTypeImageIndex, MediaTypeDockerSchema2ManifestList: 265 sort.SliceStable(children, func(i, j int) bool { 266 if children[i].Platform == nil { 267 return false 268 } 269 if children[j].Platform == nil { 270 return true 271 } 272 return m.Less(*children[i].Platform, *children[j].Platform) 273 }) 274 275 if n > 0 { 276 if len(children) == 0 { 277 return children, errors.Wrap(errdefs.ErrNotFound, "no match for platform in manifest") 278 } 279 if len(children) > n { 280 children = children[:n] 281 } 282 } 283 default: 284 // only limit manifests from an index 285 } 286 return children, nil 287 } 288 }