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  }