github.com/rawahars/moby@v24.0.4+incompatible/daemon/containerd/image.go (about)

     1  package containerd
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"regexp"
     7  	"sort"
     8  	"strconv"
     9  	"sync/atomic"
    10  	"time"
    11  
    12  	cerrdefs "github.com/containerd/containerd/errdefs"
    13  	containerdimages "github.com/containerd/containerd/images"
    14  	cplatforms "github.com/containerd/containerd/platforms"
    15  	"github.com/docker/distribution/reference"
    16  	containertypes "github.com/docker/docker/api/types/container"
    17  	imagetype "github.com/docker/docker/api/types/image"
    18  	"github.com/docker/docker/daemon/images"
    19  	"github.com/docker/docker/errdefs"
    20  	"github.com/docker/docker/image"
    21  	"github.com/docker/docker/layer"
    22  	"github.com/docker/docker/pkg/platforms"
    23  	"github.com/docker/go-connections/nat"
    24  	"github.com/opencontainers/go-digest"
    25  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    26  	"github.com/pkg/errors"
    27  	"github.com/sirupsen/logrus"
    28  	"golang.org/x/sync/semaphore"
    29  )
    30  
    31  var truncatedID = regexp.MustCompile(`^([a-f0-9]{4,64})$`)
    32  
    33  // GetImage returns an image corresponding to the image referred to by refOrID.
    34  func (i *ImageService) GetImage(ctx context.Context, refOrID string, options imagetype.GetImageOpts) (*image.Image, error) {
    35  	desc, err := i.resolveImage(ctx, refOrID)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  
    40  	platform := platforms.AllPlatformsWithPreference(cplatforms.Default())
    41  	if options.Platform != nil {
    42  		platform = cplatforms.OnlyStrict(*options.Platform)
    43  	}
    44  
    45  	cs := i.client.ContentStore()
    46  
    47  	var presentImages []ocispec.Image
    48  	err = i.walkImageManifests(ctx, desc, func(img *ImageManifest) error {
    49  		conf, err := img.Config(ctx)
    50  		if err != nil {
    51  			return err
    52  		}
    53  		var ociimage ocispec.Image
    54  		if err := readConfig(ctx, cs, conf, &ociimage); err != nil {
    55  			return err
    56  		}
    57  		presentImages = append(presentImages, ociimage)
    58  		return nil
    59  	})
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	if len(presentImages) == 0 {
    64  		return nil, errdefs.NotFound(errors.New("failed to find image manifest"))
    65  	}
    66  
    67  	sort.SliceStable(presentImages, func(i, j int) bool {
    68  		return platform.Less(presentImages[i].Platform, presentImages[j].Platform)
    69  	})
    70  	ociimage := presentImages[0]
    71  
    72  	rootfs := image.NewRootFS()
    73  	for _, id := range ociimage.RootFS.DiffIDs {
    74  		rootfs.Append(layer.DiffID(id))
    75  	}
    76  	exposedPorts := make(nat.PortSet, len(ociimage.Config.ExposedPorts))
    77  	for k, v := range ociimage.Config.ExposedPorts {
    78  		exposedPorts[nat.Port(k)] = v
    79  	}
    80  
    81  	derefTimeSafely := func(t *time.Time) time.Time {
    82  		if t != nil {
    83  			return *t
    84  		}
    85  		return time.Time{}
    86  	}
    87  
    88  	var imgHistory []image.History
    89  	for _, h := range ociimage.History {
    90  		imgHistory = append(imgHistory, image.History{
    91  			Created:    derefTimeSafely(h.Created),
    92  			Author:     h.Author,
    93  			CreatedBy:  h.CreatedBy,
    94  			Comment:    h.Comment,
    95  			EmptyLayer: h.EmptyLayer,
    96  		})
    97  	}
    98  
    99  	img := image.NewImage(image.ID(desc.Target.Digest))
   100  	img.V1Image = image.V1Image{
   101  		ID:           string(desc.Target.Digest),
   102  		OS:           ociimage.OS,
   103  		Architecture: ociimage.Architecture,
   104  		Created:      derefTimeSafely(ociimage.Created),
   105  		Config: &containertypes.Config{
   106  			Entrypoint:   ociimage.Config.Entrypoint,
   107  			Env:          ociimage.Config.Env,
   108  			Cmd:          ociimage.Config.Cmd,
   109  			User:         ociimage.Config.User,
   110  			WorkingDir:   ociimage.Config.WorkingDir,
   111  			ExposedPorts: exposedPorts,
   112  			Volumes:      ociimage.Config.Volumes,
   113  			Labels:       ociimage.Config.Labels,
   114  			StopSignal:   ociimage.Config.StopSignal,
   115  		},
   116  	}
   117  
   118  	img.RootFS = rootfs
   119  	img.History = imgHistory
   120  
   121  	if options.Details {
   122  		lastUpdated := time.Unix(0, 0)
   123  		size, err := i.size(ctx, desc.Target, platform)
   124  		if err != nil {
   125  			return nil, err
   126  		}
   127  
   128  		tagged, err := i.client.ImageService().List(ctx, "target.digest=="+desc.Target.Digest.String())
   129  		if err != nil {
   130  			return nil, err
   131  		}
   132  
   133  		// Each image will result in 2 references (named and digested).
   134  		refs := make([]reference.Named, 0, len(tagged)*2)
   135  		for _, i := range tagged {
   136  			if i.UpdatedAt.After(lastUpdated) {
   137  				lastUpdated = i.UpdatedAt
   138  			}
   139  			if isDanglingImage(i) {
   140  				if len(tagged) > 1 {
   141  					// This is unexpected - dangling image should be deleted
   142  					// as soon as another image with the same target is created.
   143  					// Log a warning, but don't error out the whole operation.
   144  					logrus.WithField("refs", tagged).Warn("multiple images have the same target, but one of them is still dangling")
   145  				}
   146  				continue
   147  			}
   148  
   149  			name, err := reference.ParseNamed(i.Name)
   150  			if err != nil {
   151  				// This is inconsistent with `docker image ls` which will
   152  				// still include the malformed name in RepoTags.
   153  				logrus.WithField("name", name).WithError(err).Error("failed to parse image name as reference")
   154  				continue
   155  			}
   156  			refs = append(refs, name)
   157  
   158  			digested, err := reference.WithDigest(reference.TrimNamed(name), desc.Target.Digest)
   159  			if err != nil {
   160  				// This could only happen if digest is invalid, but considering that
   161  				// we get it from the Descriptor it's highly unlikely.
   162  				// Log error just in case.
   163  				logrus.WithError(err).Error("failed to create digested reference")
   164  				continue
   165  			}
   166  			refs = append(refs, digested)
   167  		}
   168  
   169  		img.Details = &image.Details{
   170  			References:  refs,
   171  			Size:        size,
   172  			Metadata:    nil,
   173  			Driver:      i.snapshotter,
   174  			LastUpdated: lastUpdated,
   175  		}
   176  	}
   177  
   178  	return img, nil
   179  }
   180  
   181  func (i *ImageService) GetImageManifest(ctx context.Context, refOrID string, options imagetype.GetImageOpts) (*ocispec.Descriptor, error) {
   182  	cs := i.client.ContentStore()
   183  
   184  	desc, err := i.resolveDescriptor(ctx, refOrID)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  
   189  	if containerdimages.IsManifestType(desc.MediaType) {
   190  		return &desc, nil
   191  	}
   192  
   193  	if containerdimages.IsIndexType(desc.MediaType) {
   194  		platform := platforms.AllPlatformsWithPreference(cplatforms.Default())
   195  		if options.Platform != nil {
   196  			platform = cplatforms.Only(*options.Platform)
   197  		}
   198  
   199  		childManifests, err := containerdimages.LimitManifests(containerdimages.ChildrenHandler(cs), platform, 1)(ctx, desc)
   200  		if err != nil {
   201  			if cerrdefs.IsNotFound(err) {
   202  				return nil, errdefs.NotFound(err)
   203  			}
   204  			return nil, errdefs.System(err)
   205  		}
   206  
   207  		// len(childManifests) == 1 since we requested 1 and if none
   208  		// were found LimitManifests would have thrown an error
   209  		if !containerdimages.IsManifestType(childManifests[0].MediaType) {
   210  			return nil, errdefs.NotFound(fmt.Errorf("manifest has incorrect mediatype: %s", childManifests[0].MediaType))
   211  		}
   212  
   213  		return &childManifests[0], nil
   214  	}
   215  
   216  	return nil, errdefs.NotFound(errors.New("failed to find manifest"))
   217  }
   218  
   219  // size returns the total size of the image's packed resources.
   220  func (i *ImageService) size(ctx context.Context, desc ocispec.Descriptor, platform cplatforms.MatchComparer) (int64, error) {
   221  	var size int64
   222  
   223  	cs := i.client.ContentStore()
   224  	handler := containerdimages.LimitManifests(containerdimages.ChildrenHandler(cs), platform, 1)
   225  
   226  	var wh containerdimages.HandlerFunc = func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   227  		children, err := handler(ctx, desc)
   228  		if err != nil {
   229  			if !cerrdefs.IsNotFound(err) {
   230  				return nil, err
   231  			}
   232  		}
   233  
   234  		atomic.AddInt64(&size, desc.Size)
   235  
   236  		return children, nil
   237  	}
   238  
   239  	l := semaphore.NewWeighted(3)
   240  	if err := containerdimages.Dispatch(ctx, wh, l, desc); err != nil {
   241  		return 0, err
   242  	}
   243  
   244  	return size, nil
   245  }
   246  
   247  // resolveDescriptor searches for a descriptor based on the given
   248  // reference or identifier. Returns the descriptor of
   249  // the image, which could be a manifest list, manifest, or config.
   250  func (i *ImageService) resolveDescriptor(ctx context.Context, refOrID string) (ocispec.Descriptor, error) {
   251  	img, err := i.resolveImage(ctx, refOrID)
   252  	if err != nil {
   253  		return ocispec.Descriptor{}, err
   254  	}
   255  
   256  	return img.Target, nil
   257  }
   258  
   259  func (i *ImageService) resolveImage(ctx context.Context, refOrID string) (containerdimages.Image, error) {
   260  	parsed, err := reference.ParseAnyReference(refOrID)
   261  	if err != nil {
   262  		return containerdimages.Image{}, errdefs.InvalidParameter(err)
   263  	}
   264  
   265  	is := i.client.ImageService()
   266  
   267  	digested, ok := parsed.(reference.Digested)
   268  	if ok {
   269  		imgs, err := is.List(ctx, "target.digest=="+digested.Digest().String())
   270  		if err != nil {
   271  			return containerdimages.Image{}, errors.Wrap(err, "failed to lookup digest")
   272  		}
   273  		if len(imgs) == 0 {
   274  			return containerdimages.Image{}, images.ErrImageDoesNotExist{Ref: parsed}
   275  		}
   276  
   277  		return imgs[0], nil
   278  	}
   279  
   280  	ref := reference.TagNameOnly(parsed.(reference.Named)).String()
   281  	img, err := is.Get(ctx, ref)
   282  	if err == nil {
   283  		return img, nil
   284  	} else {
   285  		// TODO(containerd): error translation can use common function
   286  		if !cerrdefs.IsNotFound(err) {
   287  			return containerdimages.Image{}, err
   288  		}
   289  	}
   290  
   291  	// If the identifier could be a short ID, attempt to match
   292  	if truncatedID.MatchString(refOrID) {
   293  		filters := []string{
   294  			fmt.Sprintf("name==%q", ref), // Or it could just look like one.
   295  			"target.digest~=" + strconv.Quote(fmt.Sprintf(`^sha256:%s[0-9a-fA-F]{%d}$`, regexp.QuoteMeta(refOrID), 64-len(refOrID))),
   296  		}
   297  		imgs, err := is.List(ctx, filters...)
   298  		if err != nil {
   299  			return containerdimages.Image{}, err
   300  		}
   301  
   302  		if len(imgs) == 0 {
   303  			return containerdimages.Image{}, images.ErrImageDoesNotExist{Ref: parsed}
   304  		}
   305  		if len(imgs) > 1 {
   306  			digests := map[digest.Digest]struct{}{}
   307  			for _, img := range imgs {
   308  				if img.Name == ref {
   309  					return img, nil
   310  				}
   311  				digests[img.Target.Digest] = struct{}{}
   312  			}
   313  
   314  			if len(digests) > 1 {
   315  				return containerdimages.Image{}, errdefs.NotFound(errors.New("ambiguous reference"))
   316  			}
   317  		}
   318  
   319  		return imgs[0], nil
   320  	}
   321  
   322  	return containerdimages.Image{}, images.ErrImageDoesNotExist{Ref: parsed}
   323  }