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