github.com/moby/docker@v26.1.3+incompatible/image/cache/cache.go (about) 1 package cache // import "github.com/docker/docker/image/cache" 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 "strings" 8 9 "github.com/containerd/log" 10 containertypes "github.com/docker/docker/api/types/container" 11 "github.com/docker/docker/builder" 12 "github.com/docker/docker/dockerversion" 13 "github.com/docker/docker/image" 14 "github.com/docker/docker/layer" 15 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 16 "github.com/pkg/errors" 17 ) 18 19 type ImageCacheStore interface { 20 Get(image.ID) (*image.Image, error) 21 GetByRef(ctx context.Context, refOrId string) (*image.Image, error) 22 SetParent(target, parent image.ID) error 23 GetParent(target image.ID) (image.ID, error) 24 Create(parent *image.Image, image image.Image, extraLayer layer.DiffID) (image.ID, error) 25 IsBuiltLocally(id image.ID) (bool, error) 26 Children(id image.ID) []image.ID 27 } 28 29 func New(ctx context.Context, store ImageCacheStore, cacheFrom []string) (builder.ImageCache, error) { 30 local := &LocalImageCache{store: store} 31 if len(cacheFrom) == 0 { 32 return local, nil 33 } 34 35 cache := &ImageCache{ 36 store: store, 37 localImageCache: local, 38 } 39 40 for _, ref := range cacheFrom { 41 img, err := store.GetByRef(ctx, ref) 42 if err != nil { 43 if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { 44 return nil, err 45 } 46 log.G(ctx).Warnf("Could not look up %s for cache resolution, skipping: %+v", ref, err) 47 continue 48 } 49 cache.Populate(img) 50 } 51 52 return cache, nil 53 } 54 55 // LocalImageCache is cache based on parent chain. 56 type LocalImageCache struct { 57 store ImageCacheStore 58 } 59 60 // GetCache returns the image id found in the cache 61 func (lic *LocalImageCache) GetCache(imgID string, config *containertypes.Config, platform ocispec.Platform) (string, error) { 62 return getImageIDAndError(getLocalCachedImage(lic.store, image.ID(imgID), config, platform)) 63 } 64 65 // ImageCache is cache based on history objects. Requires initial set of images. 66 type ImageCache struct { 67 sources []*image.Image 68 store ImageCacheStore 69 localImageCache *LocalImageCache 70 } 71 72 // Populate adds an image to the cache (to be queried later) 73 func (ic *ImageCache) Populate(image *image.Image) { 74 ic.sources = append(ic.sources, image) 75 } 76 77 // GetCache returns the image id found in the cache 78 func (ic *ImageCache) GetCache(parentID string, cfg *containertypes.Config, platform ocispec.Platform) (string, error) { 79 imgID, err := ic.localImageCache.GetCache(parentID, cfg, platform) 80 if err != nil { 81 return "", err 82 } 83 if imgID != "" { 84 for _, s := range ic.sources { 85 if ic.isParent(s.ID(), image.ID(imgID)) { 86 return imgID, nil 87 } 88 } 89 } 90 91 var parent *image.Image 92 lenHistory := 0 93 if parentID != "" { 94 parent, err = ic.store.Get(image.ID(parentID)) 95 if err != nil { 96 return "", errors.Wrapf(err, "unable to find image %v", parentID) 97 } 98 lenHistory = len(parent.History) 99 } 100 101 for _, target := range ic.sources { 102 if !isValidParent(target, parent) || !isValidConfig(cfg, target.History[lenHistory]) { 103 continue 104 } 105 106 if len(target.History)-1 == lenHistory { // last 107 if parent != nil { 108 if err := ic.store.SetParent(target.ID(), parent.ID()); err != nil { 109 return "", errors.Wrapf(err, "failed to set parent for %v to %v", target.ID(), parent.ID()) 110 } 111 } 112 return target.ID().String(), nil 113 } 114 115 imgID, err := ic.restoreCachedImage(parent, target, cfg) 116 if err != nil { 117 return "", errors.Wrapf(err, "failed to restore cached image from %q to %v", parentID, target.ID()) 118 } 119 120 ic.sources = []*image.Image{target} // avoid jumping to different target, tuned for safety atm 121 return imgID.String(), nil 122 } 123 124 return "", nil 125 } 126 127 func (ic *ImageCache) restoreCachedImage(parent, target *image.Image, cfg *containertypes.Config) (image.ID, error) { 128 var history []image.History 129 rootFS := image.NewRootFS() 130 lenHistory := 0 131 if parent != nil { 132 history = parent.History 133 rootFS = parent.RootFS 134 lenHistory = len(parent.History) 135 } 136 history = append(history, target.History[lenHistory]) 137 layer := getLayerForHistoryIndex(target, lenHistory) 138 if layer != "" { 139 rootFS.Append(layer) 140 } 141 142 restoredImg := image.Image{ 143 V1Image: image.V1Image{ 144 DockerVersion: dockerversion.Version, 145 Config: cfg, 146 Architecture: target.Architecture, 147 OS: target.OS, 148 Author: target.Author, 149 Created: history[len(history)-1].Created, 150 }, 151 RootFS: rootFS, 152 History: history, 153 OSFeatures: target.OSFeatures, 154 OSVersion: target.OSVersion, 155 } 156 157 imgID, err := ic.store.Create(parent, restoredImg, layer) 158 if err != nil { 159 return "", errors.Wrap(err, "failed to create cache image") 160 } 161 162 return imgID, nil 163 } 164 165 func (ic *ImageCache) isParent(imgID, parentID image.ID) bool { 166 nextParent, err := ic.store.GetParent(imgID) 167 if err != nil { 168 return false 169 } 170 if nextParent == parentID { 171 return true 172 } 173 return ic.isParent(nextParent, parentID) 174 } 175 176 func getLayerForHistoryIndex(image *image.Image, index int) layer.DiffID { 177 layerIndex := 0 178 for i, h := range image.History { 179 if i == index { 180 if h.EmptyLayer { 181 return "" 182 } 183 break 184 } 185 if !h.EmptyLayer { 186 layerIndex++ 187 } 188 } 189 return image.RootFS.DiffIDs[layerIndex] // validate? 190 } 191 192 func isValidConfig(cfg *containertypes.Config, h image.History) bool { 193 // todo: make this format better than join that loses data 194 return strings.Join(cfg.Cmd, " ") == h.CreatedBy 195 } 196 197 func isValidParent(img, parent *image.Image) bool { 198 if len(img.History) == 0 { 199 return false 200 } 201 if parent == nil || len(parent.History) == 0 && len(parent.RootFS.DiffIDs) == 0 { 202 return true 203 } 204 if len(parent.History) >= len(img.History) { 205 return false 206 } 207 if len(parent.RootFS.DiffIDs) > len(img.RootFS.DiffIDs) { 208 return false 209 } 210 211 for i, h := range parent.History { 212 if !reflect.DeepEqual(h, img.History[i]) { 213 return false 214 } 215 } 216 for i, d := range parent.RootFS.DiffIDs { 217 if d != img.RootFS.DiffIDs[i] { 218 return false 219 } 220 } 221 return true 222 } 223 224 func getImageIDAndError(img *image.Image, err error) (string, error) { 225 if img == nil || err != nil { 226 return "", err 227 } 228 return img.ID().String(), nil 229 } 230 231 // getLocalCachedImage returns the most recent created image that is a child 232 // of the image with imgID, that had the same config when it was 233 // created. nil is returned if a child cannot be found. An error is 234 // returned if the parent image cannot be found. 235 func getLocalCachedImage(imageStore ImageCacheStore, parentID image.ID, config *containertypes.Config, platform ocispec.Platform) (*image.Image, error) { 236 if config == nil { 237 return nil, nil 238 } 239 240 var match *image.Image 241 for _, id := range imageStore.Children(parentID) { 242 img, err := imageStore.Get(id) 243 if err != nil { 244 return nil, fmt.Errorf("unable to find image %q", id) 245 } 246 247 builtLocally, err := imageStore.IsBuiltLocally(id) 248 if err != nil { 249 log.G(context.TODO()).WithFields(log.Fields{ 250 "error": err, 251 "id": id, 252 }).Warn("failed to check if image was built locally") 253 continue 254 } 255 if !builtLocally { 256 continue 257 } 258 259 imgPlatform := img.Platform() 260 // Discard old linux/amd64 images with empty platform. 261 if imgPlatform.OS == "" && imgPlatform.Architecture == "" { 262 continue 263 } 264 if !comparePlatform(platform, imgPlatform) { 265 continue 266 } 267 268 if compare(&img.ContainerConfig, config) { 269 // check for the most up to date match 270 if img.Created != nil && (match == nil || match.Created.Before(*img.Created)) { 271 match = img 272 } 273 } 274 } 275 return match, nil 276 }