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