github.com/moby/docker@v26.1.3+incompatible/daemon/containerd/image_history.go (about)

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