github.com/rish1988/moby@v25.0.2+incompatible/daemon/containerd/image_history.go (about)

     1  package containerd
     2  
     3  import (
     4  	"context"
     5  	"sort"
     6  
     7  	"github.com/containerd/containerd/images"
     8  	containerdimages "github.com/containerd/containerd/images"
     9  	"github.com/containerd/containerd/platforms"
    10  	"github.com/containerd/log"
    11  	"github.com/distribution/reference"
    12  	imagetype "github.com/docker/docker/api/types/image"
    13  	"github.com/docker/docker/errdefs"
    14  	"github.com/opencontainers/go-digest"
    15  	"github.com/opencontainers/image-spec/identity"
    16  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  // ImageHistory returns a slice of HistoryResponseItem structures for the
    21  // specified image name by walking the image lineage.
    22  func (i *ImageService) ImageHistory(ctx context.Context, name string) ([]*imagetype.HistoryResponseItem, error) {
    23  	img, err := i.resolveImage(ctx, name)
    24  	if err != nil {
    25  		return nil, err
    26  	}
    27  
    28  	cs := i.client.ContentStore()
    29  	// TODO: pass platform in from the CLI
    30  	platform := matchAllWithPreference(platforms.Default())
    31  
    32  	var presentImages []ocispec.Image
    33  	err = i.walkImageManifests(ctx, img, func(img *ImageManifest) error {
    34  		conf, err := img.Config(ctx)
    35  		if err != nil {
    36  			return err
    37  		}
    38  		var ociImage ocispec.Image
    39  		if err := readConfig(ctx, cs, conf, &ociImage); err != nil {
    40  			return err
    41  		}
    42  		presentImages = append(presentImages, ociImage)
    43  		return nil
    44  	})
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	if len(presentImages) == 0 {
    49  		return nil, errdefs.NotFound(errors.New("failed to find image manifest"))
    50  	}
    51  
    52  	sort.SliceStable(presentImages, func(i, j int) bool {
    53  		return platform.Less(presentImages[i].Platform, presentImages[j].Platform)
    54  	})
    55  	ociImage := presentImages[0]
    56  
    57  	var (
    58  		history []*imagetype.HistoryResponseItem
    59  		sizes   []int64
    60  	)
    61  	s := i.client.SnapshotService(i.snapshotter)
    62  
    63  	diffIDs := ociImage.RootFS.DiffIDs
    64  	for i := range diffIDs {
    65  		chainID := identity.ChainID(diffIDs[0 : i+1]).String()
    66  
    67  		use, err := s.Usage(ctx, chainID)
    68  		if err != nil {
    69  			return nil, err
    70  		}
    71  
    72  		sizes = append(sizes, use.Size)
    73  	}
    74  
    75  	for _, h := range ociImage.History {
    76  		size := int64(0)
    77  		if !h.EmptyLayer {
    78  			if len(sizes) == 0 {
    79  				return nil, errors.New("unable to find the size of the layer")
    80  			}
    81  			size = sizes[0]
    82  			sizes = sizes[1:]
    83  		}
    84  
    85  		var created int64
    86  		if h.Created != nil {
    87  			created = h.Created.Unix()
    88  		}
    89  		history = append([]*imagetype.HistoryResponseItem{{
    90  			ID:        "<missing>",
    91  			Comment:   h.Comment,
    92  			CreatedBy: h.CreatedBy,
    93  			Created:   created,
    94  			Size:      size,
    95  			Tags:      nil,
    96  		}}, history...)
    97  	}
    98  
    99  	findParents := func(img images.Image) []images.Image {
   100  		imgs, err := i.getParentsByBuilderLabel(ctx, img)
   101  		if err != nil {
   102  			log.G(ctx).WithFields(log.Fields{
   103  				"error": err,
   104  				"image": img,
   105  			}).Warn("failed to list parent images")
   106  			return nil
   107  		}
   108  		return imgs
   109  	}
   110  
   111  	is := i.client.ImageService()
   112  	currentImg := img
   113  	for _, h := range history {
   114  		dgst := currentImg.Target.Digest.String()
   115  		h.ID = dgst
   116  
   117  		imgs, err := is.List(ctx, "target.digest=="+dgst)
   118  		if err != nil {
   119  			return nil, err
   120  		}
   121  
   122  		tags := getImageTags(ctx, imgs)
   123  		h.Tags = append(h.Tags, tags...)
   124  
   125  		parents := findParents(currentImg)
   126  
   127  		foundNext := false
   128  		for _, img := range parents {
   129  			_, hasLabel := img.Labels[imageLabelClassicBuilderParent]
   130  			if !foundNext || hasLabel {
   131  				currentImg = img
   132  				foundNext = true
   133  			}
   134  		}
   135  
   136  		if !foundNext {
   137  			break
   138  		}
   139  	}
   140  
   141  	return history, nil
   142  }
   143  
   144  func getImageTags(ctx context.Context, imgs []images.Image) []string {
   145  	var tags []string
   146  	for _, img := range imgs {
   147  		if isDanglingImage(img) {
   148  			continue
   149  		}
   150  
   151  		name, err := reference.ParseNamed(img.Name)
   152  		if err != nil {
   153  			log.G(ctx).WithFields(log.Fields{
   154  				"name":  name,
   155  				"error": err,
   156  			}).Warn("image with a name that's not a valid named reference")
   157  			continue
   158  		}
   159  
   160  		tags = append(tags, reference.FamiliarString(name))
   161  	}
   162  
   163  	return tags
   164  }
   165  
   166  // getParentsByBuilderLabel finds images that were a base for the given image
   167  // by an image label set by the legacy builder.
   168  // NOTE: This only works for images built with legacy builder (not Buildkit).
   169  func (i *ImageService) getParentsByBuilderLabel(ctx context.Context, img containerdimages.Image) ([]containerdimages.Image, error) {
   170  	parent, ok := img.Labels[imageLabelClassicBuilderParent]
   171  	if !ok || parent == "" {
   172  		return nil, nil
   173  	}
   174  
   175  	dgst, err := digest.Parse(parent)
   176  	if err != nil {
   177  		log.G(ctx).WithFields(log.Fields{
   178  			"error": err,
   179  			"value": parent,
   180  		}).Warnf("invalid %s label value", imageLabelClassicBuilderParent)
   181  		return nil, nil
   182  	}
   183  
   184  	return i.client.ImageService().List(ctx, "target.digest=="+dgst.String())
   185  }