github.com/demonoid81/containerd@v1.3.4/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/platforms" 26 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 27 "github.com/pkg/errors" 28 "golang.org/x/sync/errgroup" 29 "golang.org/x/sync/semaphore" 30 ) 31 32 var ( 33 // ErrSkipDesc is used to skip processing of a descriptor and 34 // its descendants. 35 ErrSkipDesc = fmt.Errorf("skip descriptor") 36 37 // ErrStopHandler is used to signify that the descriptor 38 // has been handled and should not be handled further. 39 // This applies only to a single descriptor in a handler 40 // chain and does not apply to descendant descriptors. 41 ErrStopHandler = fmt.Errorf("stop handler") 42 ) 43 44 // Handler handles image manifests 45 type Handler interface { 46 Handle(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) 47 } 48 49 // HandlerFunc function implementing the Handler interface 50 type HandlerFunc func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) 51 52 // Handle image manifests 53 func (fn HandlerFunc) Handle(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) { 54 return fn(ctx, desc) 55 } 56 57 // Handlers returns a handler that will run the handlers in sequence. 58 // 59 // A handler may return `ErrStopHandler` to stop calling additional handlers 60 func Handlers(handlers ...Handler) HandlerFunc { 61 return func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) { 62 var children []ocispec.Descriptor 63 for _, handler := range handlers { 64 ch, err := handler.Handle(ctx, desc) 65 if err != nil { 66 if errors.Cause(err) == ErrStopHandler { 67 break 68 } 69 return nil, err 70 } 71 72 children = append(children, ch...) 73 } 74 75 return children, nil 76 } 77 } 78 79 // Walk the resources of an image and call the handler for each. If the handler 80 // decodes the sub-resources for each image, 81 // 82 // This differs from dispatch in that each sibling resource is considered 83 // synchronously. 84 func Walk(ctx context.Context, handler Handler, descs ...ocispec.Descriptor) error { 85 for _, desc := range descs { 86 87 children, err := handler.Handle(ctx, desc) 88 if err != nil { 89 if errors.Cause(err) == ErrSkipDesc { 90 continue // don't traverse the children. 91 } 92 return err 93 } 94 95 if len(children) > 0 { 96 if err := Walk(ctx, handler, children...); err != nil { 97 return err 98 } 99 } 100 } 101 102 return nil 103 } 104 105 // Dispatch runs the provided handler for content specified by the descriptors. 106 // If the handler decode subresources, they will be visited, as well. 107 // 108 // Handlers for siblings are run in parallel on the provided descriptors. A 109 // handler may return `ErrSkipDesc` to signal to the dispatcher to not traverse 110 // any children. 111 // 112 // A concurrency limiter can be passed in to limit the number of concurrent 113 // handlers running. When limiter is nil, there is no limit. 114 // 115 // Typically, this function will be used with `FetchHandler`, often composed 116 // with other handlers. 117 // 118 // If any handler returns an error, the dispatch session will be canceled. 119 func Dispatch(ctx context.Context, handler Handler, limiter *semaphore.Weighted, descs ...ocispec.Descriptor) error { 120 eg, ctx2 := errgroup.WithContext(ctx) 121 for _, desc := range descs { 122 desc := desc 123 124 if limiter != nil { 125 if err := limiter.Acquire(ctx, 1); err != nil { 126 return err 127 } 128 } 129 130 eg.Go(func() error { 131 desc := desc 132 133 children, err := handler.Handle(ctx2, desc) 134 if limiter != nil { 135 limiter.Release(1) 136 } 137 if err != nil { 138 if errors.Cause(err) == ErrSkipDesc { 139 return nil // don't traverse the children. 140 } 141 return err 142 } 143 144 if len(children) > 0 { 145 return Dispatch(ctx2, handler, limiter, children...) 146 } 147 148 return nil 149 }) 150 } 151 152 return eg.Wait() 153 } 154 155 // ChildrenHandler decodes well-known manifest types and returns their children. 156 // 157 // This is useful for supporting recursive fetch and other use cases where you 158 // want to do a full walk of resources. 159 // 160 // One can also replace this with another implementation to allow descending of 161 // arbitrary types. 162 func ChildrenHandler(provider content.Provider) HandlerFunc { 163 return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 164 return Children(ctx, provider, desc) 165 } 166 } 167 168 // SetChildrenLabels is a handler wrapper which sets labels for the content on 169 // the children returned by the handler and passes through the children. 170 // Must follow a handler that returns the children to be labeled. 171 func SetChildrenLabels(manager content.Manager, f HandlerFunc) HandlerFunc { 172 return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 173 children, err := f(ctx, desc) 174 if err != nil { 175 return children, err 176 } 177 178 if len(children) > 0 { 179 info := content.Info{ 180 Digest: desc.Digest, 181 Labels: map[string]string{}, 182 } 183 fields := []string{} 184 for i, ch := range children { 185 info.Labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = ch.Digest.String() 186 fields = append(fields, fmt.Sprintf("labels.containerd.io/gc.ref.content.%d", i)) 187 } 188 189 _, err := manager.Update(ctx, info, fields...) 190 if err != nil { 191 return nil, err 192 } 193 } 194 195 return children, err 196 } 197 } 198 199 // FilterPlatforms is a handler wrapper which limits the descriptors returned 200 // based on matching the specified platform matcher. 201 func FilterPlatforms(f HandlerFunc, m platforms.Matcher) HandlerFunc { 202 return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 203 children, err := f(ctx, desc) 204 if err != nil { 205 return children, err 206 } 207 208 var descs []ocispec.Descriptor 209 210 if m == nil { 211 descs = children 212 } else { 213 for _, d := range children { 214 if d.Platform == nil || m.Match(*d.Platform) { 215 descs = append(descs, d) 216 } 217 } 218 } 219 220 return descs, nil 221 } 222 } 223 224 // LimitManifests is a handler wrapper which filters the manifest descriptors 225 // returned using the provided platform. 226 // The results will be ordered according to the comparison operator and 227 // use the ordering in the manifests for equal matches. 228 // A limit of 0 or less is considered no limit. 229 func LimitManifests(f HandlerFunc, m platforms.MatchComparer, n int) HandlerFunc { 230 return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 231 children, err := f(ctx, desc) 232 if err != nil { 233 return children, err 234 } 235 236 switch desc.MediaType { 237 case ocispec.MediaTypeImageIndex, MediaTypeDockerSchema2ManifestList: 238 sort.SliceStable(children, func(i, j int) bool { 239 if children[i].Platform == nil { 240 return false 241 } 242 if children[j].Platform == nil { 243 return true 244 } 245 return m.Less(*children[i].Platform, *children[j].Platform) 246 }) 247 248 if n > 0 && len(children) > n { 249 children = children[:n] 250 } 251 default: 252 // only limit manifests from an index 253 } 254 return children, nil 255 } 256 }