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  }