github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/images/image.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 "encoding/json" 22 "sort" 23 "time" 24 25 "github.com/containerd/containerd/content" 26 "github.com/containerd/containerd/errdefs" 27 "github.com/containerd/containerd/log" 28 "github.com/containerd/containerd/platforms" 29 digest "github.com/opencontainers/go-digest" 30 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 31 "github.com/pkg/errors" 32 ) 33 34 // Image provides the model for how containerd views container images. 35 type Image struct { 36 // Name of the image. 37 // 38 // To be pulled, it must be a reference compatible with resolvers. 39 // 40 // This field is required. 41 Name string 42 43 // Labels provide runtime decoration for the image record. 44 // 45 // There is no default behavior for how these labels are propagated. They 46 // only decorate the static metadata object. 47 // 48 // This field is optional. 49 Labels map[string]string 50 51 // Target describes the root content for this image. Typically, this is 52 // a manifest, index or manifest list. 53 Target ocispec.Descriptor 54 55 CreatedAt, UpdatedAt time.Time 56 } 57 58 // DeleteOptions provide options on image delete 59 type DeleteOptions struct { 60 Synchronous bool 61 } 62 63 // DeleteOpt allows configuring a delete operation 64 type DeleteOpt func(context.Context, *DeleteOptions) error 65 66 // SynchronousDelete is used to indicate that an image deletion and removal of 67 // the image resources should occur synchronously before returning a result. 68 func SynchronousDelete() DeleteOpt { 69 return func(ctx context.Context, o *DeleteOptions) error { 70 o.Synchronous = true 71 return nil 72 } 73 } 74 75 // Store and interact with images 76 type Store interface { 77 Get(ctx context.Context, name string) (Image, error) 78 List(ctx context.Context, filters ...string) ([]Image, error) 79 Create(ctx context.Context, image Image) (Image, error) 80 81 // Update will replace the data in the store with the provided image. If 82 // one or more fieldpaths are provided, only those fields will be updated. 83 Update(ctx context.Context, image Image, fieldpaths ...string) (Image, error) 84 85 Delete(ctx context.Context, name string, opts ...DeleteOpt) error 86 } 87 88 // TODO(stevvooe): Many of these functions make strong platform assumptions, 89 // which are untrue in a lot of cases. More refactoring must be done here to 90 // make this work in all cases. 91 92 // Config resolves the image configuration descriptor. 93 // 94 // The caller can then use the descriptor to resolve and process the 95 // configuration of the image. 96 func (image *Image) Config(ctx context.Context, provider content.Provider, platform platforms.MatchComparer) (ocispec.Descriptor, error) { 97 return Config(ctx, provider, image.Target, platform) 98 } 99 100 // RootFS returns the unpacked diffids that make up and images rootfs. 101 // 102 // These are used to verify that a set of layers unpacked to the expected 103 // values. 104 func (image *Image) RootFS(ctx context.Context, provider content.Provider, platform platforms.MatchComparer) ([]digest.Digest, error) { 105 desc, err := image.Config(ctx, provider, platform) 106 if err != nil { 107 return nil, err 108 } 109 return RootFS(ctx, provider, desc) 110 } 111 112 // Size returns the total size of an image's packed resources. 113 func (image *Image) Size(ctx context.Context, provider content.Provider, platform platforms.MatchComparer) (int64, error) { 114 var size int64 115 return size, Walk(ctx, Handlers(HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 116 if desc.Size < 0 { 117 return nil, errors.Errorf("invalid size %v in %v (%v)", desc.Size, desc.Digest, desc.MediaType) 118 } 119 size += desc.Size 120 return nil, nil 121 }), LimitManifests(FilterPlatforms(ChildrenHandler(provider), platform), platform, 1)), image.Target) 122 } 123 124 type platformManifest struct { 125 p *ocispec.Platform 126 m *ocispec.Manifest 127 } 128 129 // Manifest resolves a manifest from the image for the given platform. 130 // 131 // When a manifest descriptor inside of a manifest index does not have 132 // a platform defined, the platform from the image config is considered. 133 // 134 // If the descriptor points to a non-index manifest, then the manifest is 135 // unmarshalled and returned without considering the platform inside of the 136 // config. 137 // 138 // TODO(stevvooe): This violates the current platform agnostic approach to this 139 // package by returning a specific manifest type. We'll need to refactor this 140 // to return a manifest descriptor or decide that we want to bring the API in 141 // this direction because this abstraction is not needed.` 142 func Manifest(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform platforms.MatchComparer) (ocispec.Manifest, error) { 143 var ( 144 limit = 1 145 m []platformManifest 146 wasIndex bool 147 ) 148 149 if err := Walk(ctx, HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 150 switch desc.MediaType { 151 case MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest: 152 p, err := content.ReadBlob(ctx, provider, desc) 153 if err != nil { 154 return nil, err 155 } 156 157 var manifest ocispec.Manifest 158 if err := json.Unmarshal(p, &manifest); err != nil { 159 return nil, err 160 } 161 162 if desc.Digest != image.Digest && platform != nil { 163 if desc.Platform != nil && !platform.Match(*desc.Platform) { 164 return nil, nil 165 } 166 167 if desc.Platform == nil { 168 p, err := content.ReadBlob(ctx, provider, manifest.Config) 169 if err != nil { 170 return nil, err 171 } 172 173 var image ocispec.Image 174 if err := json.Unmarshal(p, &image); err != nil { 175 return nil, err 176 } 177 178 if !platform.Match(platforms.Normalize(ocispec.Platform{OS: image.OS, Architecture: image.Architecture})) { 179 return nil, nil 180 } 181 182 } 183 } 184 185 m = append(m, platformManifest{ 186 p: desc.Platform, 187 m: &manifest, 188 }) 189 190 return nil, nil 191 case MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex: 192 p, err := content.ReadBlob(ctx, provider, desc) 193 if err != nil { 194 return nil, err 195 } 196 197 var idx ocispec.Index 198 if err := json.Unmarshal(p, &idx); err != nil { 199 return nil, err 200 } 201 202 if platform == nil { 203 return idx.Manifests, nil 204 } 205 206 var descs []ocispec.Descriptor 207 for _, d := range idx.Manifests { 208 if d.Platform == nil || platform.Match(*d.Platform) { 209 descs = append(descs, d) 210 } 211 } 212 213 sort.SliceStable(descs, func(i, j int) bool { 214 if descs[i].Platform == nil { 215 return false 216 } 217 if descs[j].Platform == nil { 218 return true 219 } 220 return platform.Less(*descs[i].Platform, *descs[j].Platform) 221 }) 222 223 wasIndex = true 224 225 if len(descs) > limit { 226 return descs[:limit], nil 227 } 228 return descs, nil 229 } 230 return nil, errors.Wrapf(errdefs.ErrNotFound, "unexpected media type %v for %v", desc.MediaType, desc.Digest) 231 }), image); err != nil { 232 return ocispec.Manifest{}, err 233 } 234 235 if len(m) == 0 { 236 err := errors.Wrapf(errdefs.ErrNotFound, "manifest %v", image.Digest) 237 if wasIndex { 238 err = errors.Wrapf(errdefs.ErrNotFound, "no match for platform in manifest %v", image.Digest) 239 } 240 return ocispec.Manifest{}, err 241 } 242 return *m[0].m, nil 243 } 244 245 // Config resolves the image configuration descriptor using a content provided 246 // to resolve child resources on the image. 247 // 248 // The caller can then use the descriptor to resolve and process the 249 // configuration of the image. 250 func Config(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform platforms.MatchComparer) (ocispec.Descriptor, error) { 251 manifest, err := Manifest(ctx, provider, image, platform) 252 if err != nil { 253 return ocispec.Descriptor{}, err 254 } 255 return manifest.Config, err 256 } 257 258 // Platforms returns one or more platforms supported by the image. 259 func Platforms(ctx context.Context, provider content.Provider, image ocispec.Descriptor) ([]ocispec.Platform, error) { 260 var platformSpecs []ocispec.Platform 261 return platformSpecs, Walk(ctx, Handlers(HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 262 if desc.Platform != nil { 263 platformSpecs = append(platformSpecs, *desc.Platform) 264 return nil, ErrSkipDesc 265 } 266 267 switch desc.MediaType { 268 case MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig: 269 p, err := content.ReadBlob(ctx, provider, desc) 270 if err != nil { 271 return nil, err 272 } 273 274 var image ocispec.Image 275 if err := json.Unmarshal(p, &image); err != nil { 276 return nil, err 277 } 278 279 platformSpecs = append(platformSpecs, 280 platforms.Normalize(ocispec.Platform{OS: image.OS, Architecture: image.Architecture})) 281 } 282 return nil, nil 283 }), ChildrenHandler(provider)), image) 284 } 285 286 // Check returns nil if the all components of an image are available in the 287 // provider for the specified platform. 288 // 289 // If available is true, the caller can assume that required represents the 290 // complete set of content required for the image. 291 // 292 // missing will have the components that are part of required but not avaiiable 293 // in the provider. 294 // 295 // If there is a problem resolving content, an error will be returned. 296 func Check(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform platforms.MatchComparer) (available bool, required, present, missing []ocispec.Descriptor, err error) { 297 mfst, err := Manifest(ctx, provider, image, platform) 298 if err != nil { 299 if errdefs.IsNotFound(err) { 300 return false, []ocispec.Descriptor{image}, nil, []ocispec.Descriptor{image}, nil 301 } 302 303 return false, nil, nil, nil, errors.Wrapf(err, "failed to check image %v", image.Digest) 304 } 305 306 // TODO(stevvooe): It is possible that referenced conponents could have 307 // children, but this is rare. For now, we ignore this and only verify 308 // that manifest components are present. 309 required = append([]ocispec.Descriptor{mfst.Config}, mfst.Layers...) 310 311 for _, desc := range required { 312 ra, err := provider.ReaderAt(ctx, desc) 313 if err != nil { 314 if errdefs.IsNotFound(err) { 315 missing = append(missing, desc) 316 continue 317 } else { 318 return false, nil, nil, nil, errors.Wrapf(err, "failed to check image %v", desc.Digest) 319 } 320 } 321 ra.Close() 322 present = append(present, desc) 323 324 } 325 326 return true, required, present, missing, nil 327 } 328 329 // Children returns the immediate children of content described by the descriptor. 330 func Children(ctx context.Context, provider content.Provider, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 331 var descs []ocispec.Descriptor 332 switch desc.MediaType { 333 case MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest: 334 p, err := content.ReadBlob(ctx, provider, desc) 335 if err != nil { 336 return nil, err 337 } 338 339 // TODO(stevvooe): We just assume oci manifest, for now. There may be 340 // subtle differences from the docker version. 341 var manifest ocispec.Manifest 342 if err := json.Unmarshal(p, &manifest); err != nil { 343 return nil, err 344 } 345 346 descs = append(descs, manifest.Config) 347 descs = append(descs, manifest.Layers...) 348 case MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex: 349 p, err := content.ReadBlob(ctx, provider, desc) 350 if err != nil { 351 return nil, err 352 } 353 354 var index ocispec.Index 355 if err := json.Unmarshal(p, &index); err != nil { 356 return nil, err 357 } 358 359 descs = append(descs, index.Manifests...) 360 default: 361 if IsLayerType(desc.MediaType) || IsKnownConfig(desc.MediaType) { 362 // childless data types. 363 return nil, nil 364 } 365 log.G(ctx).Debugf("encountered unknown type %v; children may not be fetched", desc.MediaType) 366 } 367 368 return descs, nil 369 } 370 371 // RootFS returns the unpacked diffids that make up and images rootfs. 372 // 373 // These are used to verify that a set of layers unpacked to the expected 374 // values. 375 func RootFS(ctx context.Context, provider content.Provider, configDesc ocispec.Descriptor) ([]digest.Digest, error) { 376 p, err := content.ReadBlob(ctx, provider, configDesc) 377 if err != nil { 378 return nil, err 379 } 380 381 var config ocispec.Image 382 if err := json.Unmarshal(p, &config); err != nil { 383 return nil, err 384 } 385 return config.RootFS.DiffIDs, nil 386 }