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 }