github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/daemon/containerd/image_history.go (about)

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