github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/image/cache/cache.go (about)

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