github.com/rish1988/moby@v25.0.2+incompatible/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/opencontainers/go-digest"
    15  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    16  )
    17  
    18  // MakeImageCache creates a stateful image cache.
    19  func (i *ImageService) MakeImageCache(ctx context.Context, cacheFrom []string) (builder.ImageCache, error) {
    20  	images := []*image.Image{}
    21  	if len(cacheFrom) == 0 {
    22  		return &localCache{
    23  			imageService: i,
    24  		}, nil
    25  	}
    26  
    27  	for _, c := range cacheFrom {
    28  		h, err := i.ImageHistory(ctx, c)
    29  		if err != nil {
    30  			continue
    31  		}
    32  		for _, hi := range h {
    33  			if hi.ID != "<missing>" {
    34  				im, err := i.GetImage(ctx, hi.ID, backend.GetImageOpts{})
    35  				if err != nil {
    36  					return nil, err
    37  				}
    38  				images = append(images, im)
    39  			}
    40  		}
    41  	}
    42  
    43  	return &imageCache{
    44  		lc: &localCache{
    45  			imageService: i,
    46  		},
    47  		images:       images,
    48  		imageService: i,
    49  	}, nil
    50  }
    51  
    52  type localCache struct {
    53  	imageService *ImageService
    54  }
    55  
    56  func (ic *localCache) GetCache(parentID string, cfg *container.Config) (imageID string, err error) {
    57  	ctx := context.TODO()
    58  
    59  	var children []image.ID
    60  
    61  	// FROM scratch
    62  	if parentID == "" {
    63  		c, err := ic.imageService.getImagesWithLabel(ctx, imageLabelClassicBuilderFromScratch, "1")
    64  		if err != nil {
    65  			return "", err
    66  		}
    67  		children = c
    68  	} else {
    69  		c, err := ic.imageService.Children(ctx, image.ID(parentID))
    70  		if err != nil {
    71  			return "", err
    72  		}
    73  		children = c
    74  	}
    75  
    76  	var match *image.Image
    77  	for _, child := range children {
    78  		ccDigestStr, err := ic.imageService.getImageLabelByDigest(ctx, child.Digest(), imageLabelClassicBuilderContainerConfig)
    79  		if err != nil {
    80  			return "", err
    81  		}
    82  		if ccDigestStr == "" {
    83  			continue
    84  		}
    85  
    86  		dgst, err := digest.Parse(ccDigestStr)
    87  		if err != nil {
    88  			log.G(ctx).WithError(err).Warnf("invalid container config digest: %q", ccDigestStr)
    89  			continue
    90  		}
    91  
    92  		var cc container.Config
    93  		if err := readConfig(ctx, ic.imageService.content, ocispec.Descriptor{Digest: dgst}, &cc); err != nil {
    94  			if errdefs.IsNotFound(err) {
    95  				log.G(ctx).WithError(err).WithField("image", child).Warnf("missing container config: %q", ccDigestStr)
    96  				continue
    97  			}
    98  			return "", err
    99  		}
   100  
   101  		if isMatch(&cc, cfg) {
   102  			childImage, err := ic.imageService.GetImage(ctx, child.String(), backend.GetImageOpts{})
   103  			if err != nil {
   104  				return "", err
   105  			}
   106  
   107  			if childImage.Created != nil && (match == nil || match.Created.Before(*childImage.Created)) {
   108  				match = childImage
   109  			}
   110  		}
   111  	}
   112  
   113  	if match == nil {
   114  		return "", nil
   115  	}
   116  
   117  	return match.ID().String(), nil
   118  }
   119  
   120  type imageCache struct {
   121  	images       []*image.Image
   122  	imageService *ImageService
   123  	lc           *localCache
   124  }
   125  
   126  func (ic *imageCache) GetCache(parentID string, cfg *container.Config) (imageID string, err error) {
   127  	ctx := context.TODO()
   128  
   129  	imgID, err := ic.lc.GetCache(parentID, cfg)
   130  	if err != nil {
   131  		return "", err
   132  	}
   133  	if imgID != "" {
   134  		for _, s := range ic.images {
   135  			if ic.isParent(ctx, s, image.ID(imgID)) {
   136  				return imgID, nil
   137  			}
   138  		}
   139  	}
   140  
   141  	var parent *image.Image
   142  	lenHistory := 0
   143  
   144  	if parentID != "" {
   145  		parent, err = ic.imageService.GetImage(ctx, parentID, backend.GetImageOpts{})
   146  		if err != nil {
   147  			return "", err
   148  		}
   149  		lenHistory = len(parent.History)
   150  	}
   151  	for _, target := range ic.images {
   152  		if !isValidParent(target, parent) || !isValidConfig(cfg, target.History[lenHistory]) {
   153  			continue
   154  		}
   155  		return target.ID().String(), nil
   156  	}
   157  
   158  	return "", nil
   159  }
   160  
   161  func isValidConfig(cfg *container.Config, h image.History) bool {
   162  	// todo: make this format better than join that loses data
   163  	return strings.Join(cfg.Cmd, " ") == h.CreatedBy
   164  }
   165  
   166  func isValidParent(img, parent *image.Image) bool {
   167  	if len(img.History) == 0 {
   168  		return false
   169  	}
   170  	if parent == nil || len(parent.History) == 0 && len(parent.RootFS.DiffIDs) == 0 {
   171  		return true
   172  	}
   173  	if len(parent.History) >= len(img.History) {
   174  		return false
   175  	}
   176  	if len(parent.RootFS.DiffIDs) > len(img.RootFS.DiffIDs) {
   177  		return false
   178  	}
   179  
   180  	for i, h := range parent.History {
   181  		if !reflect.DeepEqual(h, img.History[i]) {
   182  			return false
   183  		}
   184  	}
   185  	for i, d := range parent.RootFS.DiffIDs {
   186  		if d != img.RootFS.DiffIDs[i] {
   187  			return false
   188  		}
   189  	}
   190  	return true
   191  }
   192  
   193  func (ic *imageCache) isParent(ctx context.Context, img *image.Image, parentID image.ID) bool {
   194  	ii, err := ic.imageService.resolveImage(ctx, img.ImageID())
   195  	if err != nil {
   196  		return false
   197  	}
   198  	parent, ok := ii.Labels[imageLabelClassicBuilderParent]
   199  	if ok {
   200  		return parent == parentID.String()
   201  	}
   202  
   203  	p, err := ic.imageService.GetImage(ctx, parentID.String(), backend.GetImageOpts{})
   204  	if err != nil {
   205  		return false
   206  	}
   207  	return ic.isParent(ctx, p, parentID)
   208  }
   209  
   210  // compare two Config struct. Do not compare the "Image" nor "Hostname" fields
   211  // If OpenStdin is set, then it differs
   212  func isMatch(a, b *container.Config) bool {
   213  	if a == nil || b == nil ||
   214  		a.OpenStdin || b.OpenStdin {
   215  		return false
   216  	}
   217  	if a.AttachStdout != b.AttachStdout ||
   218  		a.AttachStderr != b.AttachStderr ||
   219  		a.User != b.User ||
   220  		a.OpenStdin != b.OpenStdin ||
   221  		a.Tty != b.Tty {
   222  		return false
   223  	}
   224  
   225  	if len(a.Cmd) != len(b.Cmd) ||
   226  		len(a.Env) != len(b.Env) ||
   227  		len(a.Labels) != len(b.Labels) ||
   228  		len(a.ExposedPorts) != len(b.ExposedPorts) ||
   229  		len(a.Entrypoint) != len(b.Entrypoint) ||
   230  		len(a.Volumes) != len(b.Volumes) {
   231  		return false
   232  	}
   233  
   234  	for i := 0; i < len(a.Cmd); i++ {
   235  		if a.Cmd[i] != b.Cmd[i] {
   236  			return false
   237  		}
   238  	}
   239  	for i := 0; i < len(a.Env); i++ {
   240  		if a.Env[i] != b.Env[i] {
   241  			return false
   242  		}
   243  	}
   244  	for k, v := range a.Labels {
   245  		if v != b.Labels[k] {
   246  			return false
   247  		}
   248  	}
   249  	for k := range a.ExposedPorts {
   250  		if _, exists := b.ExposedPorts[k]; !exists {
   251  			return false
   252  		}
   253  	}
   254  
   255  	for i := 0; i < len(a.Entrypoint); i++ {
   256  		if a.Entrypoint[i] != b.Entrypoint[i] {
   257  			return false
   258  		}
   259  	}
   260  	for key := range a.Volumes {
   261  		if _, exists := b.Volumes[key]; !exists {
   262  			return false
   263  		}
   264  	}
   265  	return true
   266  }