github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/daemon/containerd/cache.go (about)

     1  package containerd
     2  
     3  import (
     4  	"context"
     5  	"reflect"
     6  	"strings"
     7  
     8  	"github.com/containerd/log"
     9  	"github.com/docker/docker/api/types/backend"
    10  	"github.com/docker/docker/api/types/container"
    11  	"github.com/docker/docker/builder"
    12  	"github.com/docker/docker/errdefs"
    13  	"github.com/docker/docker/image"
    14  	"github.com/docker/docker/image/cache"
    15  	"github.com/opencontainers/go-digest"
    16  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    17  )
    18  
    19  // MakeImageCache creates a stateful image cache.
    20  func (i *ImageService) MakeImageCache(ctx context.Context, cacheFrom []string) (builder.ImageCache, error) {
    21  	images := []*image.Image{}
    22  	if len(cacheFrom) == 0 {
    23  		return &localCache{
    24  			imageService: i,
    25  		}, nil
    26  	}
    27  
    28  	for _, c := range cacheFrom {
    29  		h, err := i.ImageHistory(ctx, c)
    30  		if err != nil {
    31  			continue
    32  		}
    33  		for _, hi := range h {
    34  			if hi.ID != "<missing>" {
    35  				im, err := i.GetImage(ctx, hi.ID, backend.GetImageOpts{})
    36  				if err != nil {
    37  					return nil, err
    38  				}
    39  				images = append(images, im)
    40  			}
    41  		}
    42  	}
    43  
    44  	return &imageCache{
    45  		lc: &localCache{
    46  			imageService: i,
    47  		},
    48  		images:       images,
    49  		imageService: i,
    50  	}, nil
    51  }
    52  
    53  type localCache struct {
    54  	imageService *ImageService
    55  }
    56  
    57  func (ic *localCache) GetCache(parentID string, cfg *container.Config, platform ocispec.Platform) (imageID string, err error) {
    58  	ctx := context.TODO()
    59  
    60  	var children []image.ID
    61  
    62  	// FROM scratch
    63  	if parentID == "" {
    64  		c, err := ic.imageService.getImagesWithLabel(ctx, imageLabelClassicBuilderFromScratch, "1")
    65  		if err != nil {
    66  			return "", err
    67  		}
    68  		children = c
    69  	} else {
    70  		c, err := ic.imageService.Children(ctx, image.ID(parentID))
    71  		if err != nil {
    72  			return "", err
    73  		}
    74  		children = c
    75  	}
    76  
    77  	var match *image.Image
    78  	for _, child := range children {
    79  		ccDigestStr, err := ic.imageService.getImageLabelByDigest(ctx, child.Digest(), imageLabelClassicBuilderContainerConfig)
    80  		if err != nil {
    81  			return "", err
    82  		}
    83  		if ccDigestStr == "" {
    84  			continue
    85  		}
    86  
    87  		dgst, err := digest.Parse(ccDigestStr)
    88  		if err != nil {
    89  			log.G(ctx).WithError(err).Warnf("invalid container config digest: %q", ccDigestStr)
    90  			continue
    91  		}
    92  
    93  		var cc container.Config
    94  		if err := readConfig(ctx, ic.imageService.content, ocispec.Descriptor{Digest: dgst}, &cc); err != nil {
    95  			if errdefs.IsNotFound(err) {
    96  				log.G(ctx).WithError(err).WithField("image", child).Warnf("missing container config: %q", ccDigestStr)
    97  				continue
    98  			}
    99  			return "", err
   100  		}
   101  
   102  		if cache.CompareConfig(&cc, cfg) {
   103  			childImage, err := ic.imageService.GetImage(ctx, child.String(), backend.GetImageOpts{Platform: &platform})
   104  			if err != nil {
   105  				if errdefs.IsNotFound(err) {
   106  					continue
   107  				}
   108  				return "", err
   109  			}
   110  
   111  			if childImage.Created != nil && (match == nil || match.Created.Before(*childImage.Created)) {
   112  				match = childImage
   113  			}
   114  		}
   115  	}
   116  
   117  	if match == nil {
   118  		return "", nil
   119  	}
   120  
   121  	return match.ID().String(), nil
   122  }
   123  
   124  type imageCache struct {
   125  	images       []*image.Image
   126  	imageService *ImageService
   127  	lc           *localCache
   128  }
   129  
   130  func (ic *imageCache) GetCache(parentID string, cfg *container.Config, platform ocispec.Platform) (imageID string, err error) {
   131  	ctx := context.TODO()
   132  
   133  	imgID, err := ic.lc.GetCache(parentID, cfg, platform)
   134  	if err != nil {
   135  		return "", err
   136  	}
   137  	if imgID != "" {
   138  		for _, s := range ic.images {
   139  			if ic.isParent(ctx, s, image.ID(imgID)) {
   140  				return imgID, nil
   141  			}
   142  		}
   143  	}
   144  
   145  	var parent *image.Image
   146  	lenHistory := 0
   147  
   148  	if parentID != "" {
   149  		parent, err = ic.imageService.GetImage(ctx, parentID, backend.GetImageOpts{Platform: &platform})
   150  		if err != nil {
   151  			return "", err
   152  		}
   153  		lenHistory = len(parent.History)
   154  	}
   155  	for _, target := range ic.images {
   156  		if !isValidParent(target, parent) || !isValidConfig(cfg, target.History[lenHistory]) {
   157  			continue
   158  		}
   159  		return target.ID().String(), nil
   160  	}
   161  
   162  	return "", nil
   163  }
   164  
   165  func isValidConfig(cfg *container.Config, h image.History) bool {
   166  	// todo: make this format better than join that loses data
   167  	return strings.Join(cfg.Cmd, " ") == h.CreatedBy
   168  }
   169  
   170  func isValidParent(img, parent *image.Image) bool {
   171  	if len(img.History) == 0 {
   172  		return false
   173  	}
   174  	if parent == nil || len(parent.History) == 0 && len(parent.RootFS.DiffIDs) == 0 {
   175  		return true
   176  	}
   177  	if len(parent.History) >= len(img.History) {
   178  		return false
   179  	}
   180  	if len(parent.RootFS.DiffIDs) > len(img.RootFS.DiffIDs) {
   181  		return false
   182  	}
   183  
   184  	for i, h := range parent.History {
   185  		if !reflect.DeepEqual(h, img.History[i]) {
   186  			return false
   187  		}
   188  	}
   189  	for i, d := range parent.RootFS.DiffIDs {
   190  		if d != img.RootFS.DiffIDs[i] {
   191  			return false
   192  		}
   193  	}
   194  	return true
   195  }
   196  
   197  func (ic *imageCache) isParent(ctx context.Context, img *image.Image, parentID image.ID) bool {
   198  	ii, err := ic.imageService.resolveImage(ctx, img.ImageID())
   199  	if err != nil {
   200  		return false
   201  	}
   202  	parent, ok := ii.Labels[imageLabelClassicBuilderParent]
   203  	if ok {
   204  		return parent == parentID.String()
   205  	}
   206  
   207  	p, err := ic.imageService.GetImage(ctx, parentID.String(), backend.GetImageOpts{})
   208  	if err != nil {
   209  		return false
   210  	}
   211  	return ic.isParent(ctx, p, parentID)
   212  }