github.com/rawahars/moby@v24.0.4+incompatible/daemon/containerd/cache.go (about)

     1  package containerd
     2  
     3  import (
     4  	"context"
     5  	"reflect"
     6  	"strings"
     7  
     8  	"github.com/docker/docker/api/types/container"
     9  	imagetype "github.com/docker/docker/api/types/image"
    10  	"github.com/docker/docker/builder"
    11  	"github.com/docker/docker/image"
    12  )
    13  
    14  // MakeImageCache creates a stateful image cache.
    15  func (i *ImageService) MakeImageCache(ctx context.Context, cacheFrom []string) (builder.ImageCache, error) {
    16  	images := []*image.Image{}
    17  	for _, c := range cacheFrom {
    18  		im, err := i.GetImage(ctx, c, imagetype.GetImageOpts{})
    19  		if err != nil {
    20  			return nil, err
    21  		}
    22  		images = append(images, im)
    23  	}
    24  	return &imageCache{images: images, c: i}, nil
    25  }
    26  
    27  type imageCache struct {
    28  	images []*image.Image
    29  	c      *ImageService
    30  }
    31  
    32  func (ic *imageCache) GetCache(parentID string, cfg *container.Config) (imageID string, err error) {
    33  	ctx := context.TODO()
    34  
    35  	if parentID == "" {
    36  		// TODO handle "parentless" image cache lookups ("FROM scratch")
    37  		return "", nil
    38  	}
    39  
    40  	parent, err := ic.c.GetImage(ctx, parentID, imagetype.GetImageOpts{})
    41  	if err != nil {
    42  		return "", err
    43  	}
    44  
    45  	for _, localCachedImage := range ic.images {
    46  		if isMatch(localCachedImage, parent, cfg) {
    47  			return localCachedImage.ID().String(), nil
    48  		}
    49  	}
    50  
    51  	children, err := ic.c.Children(ctx, parent.ID())
    52  	if err != nil {
    53  		return "", err
    54  	}
    55  
    56  	for _, children := range children {
    57  		childImage, err := ic.c.GetImage(ctx, children.String(), imagetype.GetImageOpts{})
    58  		if err != nil {
    59  			return "", err
    60  		}
    61  
    62  		if isMatch(childImage, parent, cfg) {
    63  			return children.String(), nil
    64  		}
    65  	}
    66  
    67  	return "", nil
    68  }
    69  
    70  // isMatch checks whether a given target can be used as cache for the given
    71  // parent image/config combination.
    72  // A target can only be an immediate child of the given parent image. For
    73  // a parent image with `n` history entries, a valid target must have `n+1`
    74  // entries and the extra entry must match the provided config
    75  func isMatch(target, parent *image.Image, cfg *container.Config) bool {
    76  	if target == nil || parent == nil || cfg == nil {
    77  		return false
    78  	}
    79  
    80  	if len(target.History) != len(parent.History)+1 ||
    81  		len(target.RootFS.DiffIDs) != len(parent.RootFS.DiffIDs)+1 {
    82  		return false
    83  	}
    84  
    85  	for i := range parent.History {
    86  		if !reflect.DeepEqual(parent.History[i], target.History[i]) {
    87  			return false
    88  		}
    89  	}
    90  
    91  	childCreatedBy := target.History[len(target.History)-1].CreatedBy
    92  	return childCreatedBy == strings.Join(cfg.Cmd, " ")
    93  }