github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/daemon/containerd/cache.go (about) 1 package containerd 2 3 import ( 4 "context" 5 "reflect" 6 "strings" 7 8 "github.com/containerd/log" 9 "github.com/docker/docker/api/types/backend" 10 "github.com/docker/docker/api/types/container" 11 "github.com/docker/docker/builder" 12 "github.com/docker/docker/errdefs" 13 "github.com/docker/docker/image" 14 "github.com/docker/docker/image/cache" 15 "github.com/opencontainers/go-digest" 16 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 17 ) 18 19 // MakeImageCache creates a stateful image cache. 20 func (i *ImageService) MakeImageCache(ctx context.Context, cacheFrom []string) (builder.ImageCache, error) { 21 images := []*image.Image{} 22 if len(cacheFrom) == 0 { 23 return &localCache{ 24 imageService: i, 25 }, nil 26 } 27 28 for _, c := range cacheFrom { 29 h, err := i.ImageHistory(ctx, c) 30 if err != nil { 31 continue 32 } 33 for _, hi := range h { 34 if hi.ID != "<missing>" { 35 im, err := i.GetImage(ctx, hi.ID, backend.GetImageOpts{}) 36 if err != nil { 37 return nil, err 38 } 39 images = append(images, im) 40 } 41 } 42 } 43 44 return &imageCache{ 45 lc: &localCache{ 46 imageService: i, 47 }, 48 images: images, 49 imageService: i, 50 }, nil 51 } 52 53 type localCache struct { 54 imageService *ImageService 55 } 56 57 func (ic *localCache) GetCache(parentID string, cfg *container.Config, platform ocispec.Platform) (imageID string, err error) { 58 ctx := context.TODO() 59 60 var children []image.ID 61 62 // FROM scratch 63 if parentID == "" { 64 c, err := ic.imageService.getImagesWithLabel(ctx, imageLabelClassicBuilderFromScratch, "1") 65 if err != nil { 66 return "", err 67 } 68 children = c 69 } else { 70 c, err := ic.imageService.Children(ctx, image.ID(parentID)) 71 if err != nil { 72 return "", err 73 } 74 children = c 75 } 76 77 var match *image.Image 78 for _, child := range children { 79 ccDigestStr, err := ic.imageService.getImageLabelByDigest(ctx, child.Digest(), imageLabelClassicBuilderContainerConfig) 80 if err != nil { 81 return "", err 82 } 83 if ccDigestStr == "" { 84 continue 85 } 86 87 dgst, err := digest.Parse(ccDigestStr) 88 if err != nil { 89 log.G(ctx).WithError(err).Warnf("invalid container config digest: %q", ccDigestStr) 90 continue 91 } 92 93 var cc container.Config 94 if err := readConfig(ctx, ic.imageService.content, ocispec.Descriptor{Digest: dgst}, &cc); err != nil { 95 if errdefs.IsNotFound(err) { 96 log.G(ctx).WithError(err).WithField("image", child).Warnf("missing container config: %q", ccDigestStr) 97 continue 98 } 99 return "", err 100 } 101 102 if cache.CompareConfig(&cc, cfg) { 103 childImage, err := ic.imageService.GetImage(ctx, child.String(), backend.GetImageOpts{Platform: &platform}) 104 if err != nil { 105 if errdefs.IsNotFound(err) { 106 continue 107 } 108 return "", err 109 } 110 111 if childImage.Created != nil && (match == nil || match.Created.Before(*childImage.Created)) { 112 match = childImage 113 } 114 } 115 } 116 117 if match == nil { 118 return "", nil 119 } 120 121 return match.ID().String(), nil 122 } 123 124 type imageCache struct { 125 images []*image.Image 126 imageService *ImageService 127 lc *localCache 128 } 129 130 func (ic *imageCache) GetCache(parentID string, cfg *container.Config, platform ocispec.Platform) (imageID string, err error) { 131 ctx := context.TODO() 132 133 imgID, err := ic.lc.GetCache(parentID, cfg, platform) 134 if err != nil { 135 return "", err 136 } 137 if imgID != "" { 138 for _, s := range ic.images { 139 if ic.isParent(ctx, s, image.ID(imgID)) { 140 return imgID, nil 141 } 142 } 143 } 144 145 var parent *image.Image 146 lenHistory := 0 147 148 if parentID != "" { 149 parent, err = ic.imageService.GetImage(ctx, parentID, backend.GetImageOpts{Platform: &platform}) 150 if err != nil { 151 return "", err 152 } 153 lenHistory = len(parent.History) 154 } 155 for _, target := range ic.images { 156 if !isValidParent(target, parent) || !isValidConfig(cfg, target.History[lenHistory]) { 157 continue 158 } 159 return target.ID().String(), nil 160 } 161 162 return "", nil 163 } 164 165 func isValidConfig(cfg *container.Config, h image.History) bool { 166 // todo: make this format better than join that loses data 167 return strings.Join(cfg.Cmd, " ") == h.CreatedBy 168 } 169 170 func isValidParent(img, parent *image.Image) bool { 171 if len(img.History) == 0 { 172 return false 173 } 174 if parent == nil || len(parent.History) == 0 && len(parent.RootFS.DiffIDs) == 0 { 175 return true 176 } 177 if len(parent.History) >= len(img.History) { 178 return false 179 } 180 if len(parent.RootFS.DiffIDs) > len(img.RootFS.DiffIDs) { 181 return false 182 } 183 184 for i, h := range parent.History { 185 if !reflect.DeepEqual(h, img.History[i]) { 186 return false 187 } 188 } 189 for i, d := range parent.RootFS.DiffIDs { 190 if d != img.RootFS.DiffIDs[i] { 191 return false 192 } 193 } 194 return true 195 } 196 197 func (ic *imageCache) isParent(ctx context.Context, img *image.Image, parentID image.ID) bool { 198 ii, err := ic.imageService.resolveImage(ctx, img.ImageID()) 199 if err != nil { 200 return false 201 } 202 parent, ok := ii.Labels[imageLabelClassicBuilderParent] 203 if ok { 204 return parent == parentID.String() 205 } 206 207 p, err := ic.imageService.GetImage(ctx, parentID.String(), backend.GetImageOpts{}) 208 if err != nil { 209 return false 210 } 211 return ic.isParent(ctx, p, parentID) 212 }