github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/image/cache/cache.go (about) 1 package cache // import "github.com/docker/docker/image/cache" 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "reflect" 8 "strings" 9 10 "github.com/containerd/log" 11 containertypes "github.com/docker/docker/api/types/container" 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 // NewLocal returns a local image cache, based on parent chain 20 func NewLocal(store image.Store) *LocalImageCache { 21 return &LocalImageCache{ 22 store: store, 23 } 24 } 25 26 // LocalImageCache is cache based on parent chain. 27 type LocalImageCache struct { 28 store image.Store 29 } 30 31 // GetCache returns the image id found in the cache 32 func (lic *LocalImageCache) GetCache(imgID string, config *containertypes.Config, platform ocispec.Platform) (string, error) { 33 return getImageIDAndError(getLocalCachedImage(lic.store, image.ID(imgID), config, platform)) 34 } 35 36 // New returns an image cache, based on history objects 37 func New(store image.Store) *ImageCache { 38 return &ImageCache{ 39 store: store, 40 localImageCache: NewLocal(store), 41 } 42 } 43 44 // ImageCache is cache based on history objects. Requires initial set of images. 45 type ImageCache struct { 46 sources []*image.Image 47 store image.Store 48 localImageCache *LocalImageCache 49 } 50 51 // Populate adds an image to the cache (to be queried later) 52 func (ic *ImageCache) Populate(image *image.Image) { 53 ic.sources = append(ic.sources, image) 54 } 55 56 // GetCache returns the image id found in the cache 57 func (ic *ImageCache) GetCache(parentID string, cfg *containertypes.Config, platform ocispec.Platform) (string, error) { 58 imgID, err := ic.localImageCache.GetCache(parentID, cfg, platform) 59 if err != nil { 60 return "", err 61 } 62 if imgID != "" { 63 for _, s := range ic.sources { 64 if ic.isParent(s.ID(), image.ID(imgID)) { 65 return imgID, nil 66 } 67 } 68 } 69 70 var parent *image.Image 71 lenHistory := 0 72 if parentID != "" { 73 parent, err = ic.store.Get(image.ID(parentID)) 74 if err != nil { 75 return "", errors.Wrapf(err, "unable to find image %v", parentID) 76 } 77 lenHistory = len(parent.History) 78 } 79 80 for _, target := range ic.sources { 81 if !isValidParent(target, parent) || !isValidConfig(cfg, target.History[lenHistory]) { 82 continue 83 } 84 85 if len(target.History)-1 == lenHistory { // last 86 if parent != nil { 87 if err := ic.store.SetParent(target.ID(), parent.ID()); err != nil { 88 return "", errors.Wrapf(err, "failed to set parent for %v to %v", target.ID(), parent.ID()) 89 } 90 } 91 return target.ID().String(), nil 92 } 93 94 imgID, err := ic.restoreCachedImage(parent, target, cfg) 95 if err != nil { 96 return "", errors.Wrapf(err, "failed to restore cached image from %q to %v", parentID, target.ID()) 97 } 98 99 ic.sources = []*image.Image{target} // avoid jumping to different target, tuned for safety atm 100 return imgID.String(), nil 101 } 102 103 return "", nil 104 } 105 106 func (ic *ImageCache) restoreCachedImage(parent, target *image.Image, cfg *containertypes.Config) (image.ID, error) { 107 var history []image.History 108 rootFS := image.NewRootFS() 109 lenHistory := 0 110 if parent != nil { 111 history = parent.History 112 rootFS = parent.RootFS 113 lenHistory = len(parent.History) 114 } 115 history = append(history, target.History[lenHistory]) 116 if layer := getLayerForHistoryIndex(target, lenHistory); layer != "" { 117 rootFS.Append(layer) 118 } 119 120 config, err := json.Marshal(&image.Image{ 121 V1Image: image.V1Image{ 122 DockerVersion: dockerversion.Version, 123 Config: cfg, 124 Architecture: target.Architecture, 125 OS: target.OS, 126 Author: target.Author, 127 Created: history[len(history)-1].Created, 128 }, 129 RootFS: rootFS, 130 History: history, 131 OSFeatures: target.OSFeatures, 132 OSVersion: target.OSVersion, 133 }) 134 if err != nil { 135 return "", errors.Wrap(err, "failed to marshal image config") 136 } 137 138 imgID, err := ic.store.Create(config) 139 if err != nil { 140 return "", errors.Wrap(err, "failed to create cache image") 141 } 142 143 if parent != nil { 144 if err := ic.store.SetParent(imgID, parent.ID()); err != nil { 145 return "", errors.Wrapf(err, "failed to set parent for %v to %v", target.ID(), parent.ID()) 146 } 147 } 148 return imgID, nil 149 } 150 151 func (ic *ImageCache) isParent(imgID, parentID image.ID) bool { 152 nextParent, err := ic.store.GetParent(imgID) 153 if err != nil { 154 return false 155 } 156 if nextParent == parentID { 157 return true 158 } 159 return ic.isParent(nextParent, parentID) 160 } 161 162 func getLayerForHistoryIndex(image *image.Image, index int) layer.DiffID { 163 layerIndex := 0 164 for i, h := range image.History { 165 if i == index { 166 if h.EmptyLayer { 167 return "" 168 } 169 break 170 } 171 if !h.EmptyLayer { 172 layerIndex++ 173 } 174 } 175 return image.RootFS.DiffIDs[layerIndex] // validate? 176 } 177 178 func isValidConfig(cfg *containertypes.Config, h image.History) bool { 179 // todo: make this format better than join that loses data 180 return strings.Join(cfg.Cmd, " ") == h.CreatedBy 181 } 182 183 func isValidParent(img, parent *image.Image) bool { 184 if len(img.History) == 0 { 185 return false 186 } 187 if parent == nil || len(parent.History) == 0 && len(parent.RootFS.DiffIDs) == 0 { 188 return true 189 } 190 if len(parent.History) >= len(img.History) { 191 return false 192 } 193 if len(parent.RootFS.DiffIDs) > len(img.RootFS.DiffIDs) { 194 return false 195 } 196 197 for i, h := range parent.History { 198 if !reflect.DeepEqual(h, img.History[i]) { 199 return false 200 } 201 } 202 for i, d := range parent.RootFS.DiffIDs { 203 if d != img.RootFS.DiffIDs[i] { 204 return false 205 } 206 } 207 return true 208 } 209 210 func getImageIDAndError(img *image.Image, err error) (string, error) { 211 if img == nil || err != nil { 212 return "", err 213 } 214 return img.ID().String(), nil 215 } 216 217 // getLocalCachedImage returns the most recent created image that is a child 218 // of the image with imgID, that had the same config when it was 219 // created. nil is returned if a child cannot be found. An error is 220 // returned if the parent image cannot be found. 221 func getLocalCachedImage(imageStore image.Store, imgID image.ID, config *containertypes.Config, platform ocispec.Platform) (*image.Image, error) { 222 if config == nil { 223 return nil, nil 224 } 225 226 isBuiltLocally := func(id image.ID) bool { 227 builtLocally, err := imageStore.IsBuiltLocally(id) 228 if err != nil { 229 log.G(context.TODO()).WithFields(log.Fields{ 230 "error": err, 231 "id": id, 232 }).Warn("failed to check if image was built locally") 233 return false 234 } 235 return builtLocally 236 } 237 238 // Loop on the children of the given image and check the config 239 getMatch := func(siblings []image.ID) (*image.Image, error) { 240 var match *image.Image 241 for _, id := range siblings { 242 img, err := imageStore.Get(id) 243 if err != nil { 244 return nil, fmt.Errorf("unable to find image %q", id) 245 } 246 247 if !isBuiltLocally(id) { 248 continue 249 } 250 251 imgPlatform := img.Platform() 252 253 // Discard old linux/amd64 images with empty platform. 254 if imgPlatform.OS == "" && imgPlatform.Architecture == "" { 255 continue 256 } 257 if !comparePlatform(platform, imgPlatform) { 258 continue 259 } 260 261 if compare(&img.ContainerConfig, config) { 262 // check for the most up to date match 263 if img.Created != nil && (match == nil || match.Created.Before(*img.Created)) { 264 match = img 265 } 266 } 267 } 268 return match, nil 269 } 270 271 // In this case, this is `FROM scratch`, which isn't an actual image. 272 if imgID == "" { 273 images := imageStore.Map() 274 275 var siblings []image.ID 276 for id, img := range images { 277 if img.Parent != "" { 278 continue 279 } 280 281 if !isBuiltLocally(id) { 282 continue 283 } 284 285 // Do a quick initial filter on the Cmd to avoid adding all 286 // non-local images with empty parent to the siblings slice and 287 // performing a full config compare. 288 // 289 // config.Cmd is set to the current Dockerfile instruction so we 290 // check it against the img.ContainerConfig.Cmd which is the 291 // command of the last layer. 292 if !strSliceEqual(img.ContainerConfig.Cmd, config.Cmd) { 293 continue 294 } 295 296 siblings = append(siblings, id) 297 } 298 return getMatch(siblings) 299 } 300 301 // find match from child images 302 siblings := imageStore.Children(imgID) 303 return getMatch(siblings) 304 } 305 306 func strSliceEqual(a, b []string) bool { 307 if len(a) != len(b) { 308 return false 309 } 310 for i := 0; i < len(a); i++ { 311 if a[i] != b[i] { 312 return false 313 } 314 } 315 return true 316 }