github.com/kinvolk/docker@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  }