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