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 }