github.com/moby/docker@v26.1.3+incompatible/image/cache/cache.go (about)

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