github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/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 containerd 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 "sync/atomic" 24 25 "github.com/containerd/containerd/content" 26 "github.com/containerd/containerd/diff" 27 "github.com/containerd/containerd/errdefs" 28 "github.com/containerd/containerd/images" 29 "github.com/containerd/containerd/platforms" 30 "github.com/containerd/containerd/rootfs" 31 "github.com/containerd/containerd/snapshots" 32 "github.com/opencontainers/go-digest" 33 "github.com/opencontainers/image-spec/identity" 34 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 35 "github.com/pkg/errors" 36 "golang.org/x/sync/semaphore" 37 ) 38 39 // Image describes an image used by containers 40 type Image interface { 41 // Name of the image 42 Name() string 43 // Target descriptor for the image content 44 Target() ocispec.Descriptor 45 // Labels of the image 46 Labels() map[string]string 47 // Unpack unpacks the image's content into a snapshot 48 Unpack(context.Context, string, ...UnpackOpt) error 49 // RootFS returns the unpacked diffids that make up images rootfs. 50 RootFS(ctx context.Context) ([]digest.Digest, error) 51 // Size returns the total size of the image's packed resources. 52 Size(ctx context.Context) (int64, error) 53 // Usage returns a usage calculation for the image. 54 Usage(context.Context, ...UsageOpt) (int64, error) 55 // Config descriptor for the image. 56 Config(ctx context.Context) (ocispec.Descriptor, error) 57 // IsUnpacked returns whether or not an image is unpacked. 58 IsUnpacked(context.Context, string) (bool, error) 59 // ContentStore provides a content store which contains image blob data 60 ContentStore() content.Store 61 // Metadata returns the underlying image metadata 62 Metadata() images.Image 63 } 64 65 type usageOptions struct { 66 manifestLimit *int 67 manifestOnly bool 68 snapshots bool 69 } 70 71 // UsageOpt is used to configure the usage calculation 72 type UsageOpt func(*usageOptions) error 73 74 // WithUsageManifestLimit sets the limit to the number of manifests which will 75 // be walked for usage. Setting this value to 0 will require all manifests to 76 // be walked, returning ErrNotFound if manifests are missing. 77 // NOTE: By default all manifests which exist will be walked 78 // and any non-existent manifests and their subobjects will be ignored. 79 func WithUsageManifestLimit(i int) UsageOpt { 80 // If 0 then don't filter any manifests 81 // By default limits to current platform 82 return func(o *usageOptions) error { 83 o.manifestLimit = &i 84 return nil 85 } 86 } 87 88 // WithSnapshotUsage will check for referenced snapshots from the image objects 89 // and include the snapshot size in the total usage. 90 func WithSnapshotUsage() UsageOpt { 91 return func(o *usageOptions) error { 92 o.snapshots = true 93 return nil 94 } 95 } 96 97 // WithManifestUsage is used to get the usage for an image based on what is 98 // reported by the manifests rather than what exists in the content store. 99 // NOTE: This function is best used with the manifest limit set to get a 100 // consistent value, otherwise non-existent manifests will be excluded. 101 func WithManifestUsage() UsageOpt { 102 return func(o *usageOptions) error { 103 o.manifestOnly = true 104 return nil 105 } 106 } 107 108 var _ = (Image)(&image{}) 109 110 // NewImage returns a client image object from the metadata image 111 func NewImage(client *Client, i images.Image) Image { 112 return &image{ 113 client: client, 114 i: i, 115 platform: client.platform, 116 } 117 } 118 119 // NewImageWithPlatform returns a client image object from the metadata image 120 func NewImageWithPlatform(client *Client, i images.Image, platform platforms.MatchComparer) Image { 121 return &image{ 122 client: client, 123 i: i, 124 platform: platform, 125 } 126 } 127 128 type image struct { 129 client *Client 130 131 i images.Image 132 platform platforms.MatchComparer 133 } 134 135 func (i *image) Metadata() images.Image { 136 return i.i 137 } 138 139 func (i *image) Name() string { 140 return i.i.Name 141 } 142 143 func (i *image) Target() ocispec.Descriptor { 144 return i.i.Target 145 } 146 147 func (i *image) Labels() map[string]string { 148 return i.i.Labels 149 } 150 151 func (i *image) RootFS(ctx context.Context) ([]digest.Digest, error) { 152 provider := i.client.ContentStore() 153 return i.i.RootFS(ctx, provider, i.platform) 154 } 155 156 func (i *image) Size(ctx context.Context) (int64, error) { 157 return i.Usage(ctx, WithUsageManifestLimit(1), WithManifestUsage()) 158 } 159 160 func (i *image) Usage(ctx context.Context, opts ...UsageOpt) (int64, error) { 161 var config usageOptions 162 for _, opt := range opts { 163 if err := opt(&config); err != nil { 164 return 0, err 165 } 166 } 167 168 var ( 169 provider = i.client.ContentStore() 170 handler = images.ChildrenHandler(provider) 171 size int64 172 mustExist bool 173 ) 174 175 if config.manifestLimit != nil { 176 handler = images.LimitManifests(handler, i.platform, *config.manifestLimit) 177 mustExist = true 178 } 179 180 var wh images.HandlerFunc = func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 181 var usage int64 182 children, err := handler(ctx, desc) 183 if err != nil { 184 if !errdefs.IsNotFound(err) || mustExist { 185 return nil, err 186 } 187 if !config.manifestOnly { 188 // Do not count size of non-existent objects 189 desc.Size = 0 190 } 191 } else if config.snapshots || !config.manifestOnly { 192 info, err := provider.Info(ctx, desc.Digest) 193 if err != nil { 194 if !errdefs.IsNotFound(err) { 195 return nil, err 196 } 197 if !config.manifestOnly { 198 // Do not count size of non-existent objects 199 desc.Size = 0 200 } 201 } else if info.Size > desc.Size { 202 // Count actual usage, Size may be unset or -1 203 desc.Size = info.Size 204 } 205 206 if config.snapshots { 207 for k, v := range info.Labels { 208 const prefix = "containerd.io/gc.ref.snapshot." 209 if !strings.HasPrefix(k, prefix) { 210 continue 211 } 212 213 sn := i.client.SnapshotService(k[len(prefix):]) 214 if sn == nil { 215 continue 216 } 217 218 u, err := sn.Usage(ctx, v) 219 if err != nil { 220 if !errdefs.IsNotFound(err) && !errdefs.IsInvalidArgument(err) { 221 return nil, err 222 } 223 } else { 224 usage += u.Size 225 } 226 } 227 } 228 } 229 230 // Ignore unknown sizes. Generally unknown sizes should 231 // never be set in manifests, however, the usage 232 // calculation does not need to enforce this. 233 if desc.Size >= 0 { 234 usage += desc.Size 235 } 236 237 atomic.AddInt64(&size, usage) 238 239 return children, nil 240 } 241 242 l := semaphore.NewWeighted(3) 243 if err := images.Dispatch(ctx, wh, l, i.i.Target); err != nil { 244 return 0, err 245 } 246 247 return size, nil 248 } 249 250 func (i *image) Config(ctx context.Context) (ocispec.Descriptor, error) { 251 provider := i.client.ContentStore() 252 return i.i.Config(ctx, provider, i.platform) 253 } 254 255 func (i *image) IsUnpacked(ctx context.Context, snapshotterName string) (bool, error) { 256 sn, err := i.client.getSnapshotter(ctx, snapshotterName) 257 if err != nil { 258 return false, err 259 } 260 cs := i.client.ContentStore() 261 262 diffs, err := i.i.RootFS(ctx, cs, i.platform) 263 if err != nil { 264 return false, err 265 } 266 267 chainID := identity.ChainID(diffs) 268 _, err = sn.Stat(ctx, chainID.String()) 269 if err == nil { 270 return true, nil 271 } else if !errdefs.IsNotFound(err) { 272 return false, err 273 } 274 275 return false, nil 276 } 277 278 // UnpackConfig provides configuration for the unpack of an image 279 type UnpackConfig struct { 280 // ApplyOpts for applying a diff to a snapshotter 281 ApplyOpts []diff.ApplyOpt 282 // SnapshotOpts for configuring a snapshotter 283 SnapshotOpts []snapshots.Opt 284 } 285 286 // UnpackOpt provides configuration for unpack 287 type UnpackOpt func(context.Context, *UnpackConfig) error 288 289 func (i *image) Unpack(ctx context.Context, snapshotterName string, opts ...UnpackOpt) error { 290 ctx, done, err := i.client.WithLease(ctx) 291 if err != nil { 292 return err 293 } 294 defer done(ctx) 295 296 var config UnpackConfig 297 for _, o := range opts { 298 if err := o(ctx, &config); err != nil { 299 return err 300 } 301 } 302 303 layers, err := i.getLayers(ctx, i.platform) 304 if err != nil { 305 return err 306 } 307 308 var ( 309 a = i.client.DiffService() 310 cs = i.client.ContentStore() 311 312 chain []digest.Digest 313 unpacked bool 314 ) 315 snapshotterName, err = i.client.resolveSnapshotterName(ctx, snapshotterName) 316 if err != nil { 317 return err 318 } 319 sn, err := i.client.getSnapshotter(ctx, snapshotterName) 320 if err != nil { 321 return err 322 } 323 for _, layer := range layers { 324 unpacked, err = rootfs.ApplyLayerWithOpts(ctx, layer, chain, sn, a, config.SnapshotOpts, config.ApplyOpts) 325 if err != nil { 326 return err 327 } 328 329 if unpacked { 330 // Set the uncompressed label after the uncompressed 331 // digest has been verified through apply. 332 cinfo := content.Info{ 333 Digest: layer.Blob.Digest, 334 Labels: map[string]string{ 335 "containerd.io/uncompressed": layer.Diff.Digest.String(), 336 }, 337 } 338 if _, err := cs.Update(ctx, cinfo, "labels.containerd.io/uncompressed"); err != nil { 339 return err 340 } 341 } 342 343 chain = append(chain, layer.Diff.Digest) 344 } 345 346 desc, err := i.i.Config(ctx, cs, i.platform) 347 if err != nil { 348 return err 349 } 350 351 rootfs := identity.ChainID(chain).String() 352 353 cinfo := content.Info{ 354 Digest: desc.Digest, 355 Labels: map[string]string{ 356 fmt.Sprintf("containerd.io/gc.ref.snapshot.%s", snapshotterName): rootfs, 357 }, 358 } 359 360 _, err = cs.Update(ctx, cinfo, fmt.Sprintf("labels.containerd.io/gc.ref.snapshot.%s", snapshotterName)) 361 return err 362 } 363 364 func (i *image) getLayers(ctx context.Context, platform platforms.MatchComparer) ([]rootfs.Layer, error) { 365 cs := i.client.ContentStore() 366 367 manifest, err := images.Manifest(ctx, cs, i.i.Target, platform) 368 if err != nil { 369 return nil, err 370 } 371 372 diffIDs, err := i.i.RootFS(ctx, cs, platform) 373 if err != nil { 374 return nil, errors.Wrap(err, "failed to resolve rootfs") 375 } 376 if len(diffIDs) != len(manifest.Layers) { 377 return nil, errors.Errorf("mismatched image rootfs and manifest layers") 378 } 379 layers := make([]rootfs.Layer, len(diffIDs)) 380 for i := range diffIDs { 381 layers[i].Diff = ocispec.Descriptor{ 382 // TODO: derive media type from compressed type 383 MediaType: ocispec.MediaTypeImageLayer, 384 Digest: diffIDs[i], 385 } 386 layers[i].Blob = manifest.Layers[i] 387 } 388 return layers, nil 389 } 390 391 func (i *image) ContentStore() content.Store { 392 return i.client.ContentStore() 393 }