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

     1  package containerd
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  
     9  	"github.com/containerd/containerd/content"
    10  	cerrdefs "github.com/containerd/containerd/errdefs"
    11  	"github.com/containerd/log"
    12  	"github.com/docker/docker/api/types/backend"
    13  	"github.com/docker/docker/api/types/container"
    14  	"github.com/docker/docker/builder"
    15  	"github.com/docker/docker/errdefs"
    16  	"github.com/docker/docker/image"
    17  	"github.com/docker/docker/image/cache"
    18  	"github.com/docker/docker/internal/multierror"
    19  	"github.com/docker/docker/layer"
    20  	"github.com/opencontainers/go-digest"
    21  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    22  )
    23  
    24  // MakeImageCache creates a stateful image cache.
    25  func (i *ImageService) MakeImageCache(ctx context.Context, sourceRefs []string) (builder.ImageCache, error) {
    26  	return cache.New(ctx, cacheAdaptor{i}, sourceRefs)
    27  }
    28  
    29  type cacheAdaptor struct {
    30  	is *ImageService
    31  }
    32  
    33  func (c cacheAdaptor) Get(id image.ID) (*image.Image, error) {
    34  	ctx := context.TODO()
    35  	ref := id.String()
    36  
    37  	outImg, err := c.is.GetImage(ctx, id.String(), backend.GetImageOpts{})
    38  	if err != nil {
    39  		return nil, fmt.Errorf("GetImage: %w", err)
    40  	}
    41  
    42  	c8dImg, err := c.is.resolveImage(ctx, ref)
    43  	if err != nil {
    44  		return nil, fmt.Errorf("resolveImage: %w", err)
    45  	}
    46  
    47  	var errFound = errors.New("success")
    48  	err = c.is.walkImageManifests(ctx, c8dImg, func(img *ImageManifest) error {
    49  		desc, err := img.Config(ctx)
    50  		if err != nil {
    51  			log.G(ctx).WithFields(log.Fields{
    52  				"image": img,
    53  				"error": err,
    54  			}).Warn("failed to get config descriptor for image")
    55  			return nil
    56  		}
    57  
    58  		info, err := c.is.content.Info(ctx, desc.Digest)
    59  		if err != nil {
    60  			if !cerrdefs.IsNotFound(err) {
    61  				log.G(ctx).WithFields(log.Fields{
    62  					"image": img,
    63  					"desc":  desc,
    64  					"error": err,
    65  				}).Warn("failed to get info of image config")
    66  			}
    67  			return nil
    68  		}
    69  
    70  		if dgstStr, ok := info.Labels[contentLabelGcRefContainerConfig]; ok {
    71  			dgst, err := digest.Parse(dgstStr)
    72  			if err != nil {
    73  				log.G(ctx).WithFields(log.Fields{
    74  					"label":   contentLabelClassicBuilderImage,
    75  					"value":   dgstStr,
    76  					"content": desc.Digest,
    77  					"error":   err,
    78  				}).Warn("invalid digest in label")
    79  				return nil
    80  			}
    81  
    82  			configDesc := ocispec.Descriptor{
    83  				Digest: dgst,
    84  			}
    85  
    86  			var config container.Config
    87  			if err := readConfig(ctx, c.is.content, configDesc, &config); err != nil {
    88  				if !errdefs.IsNotFound(err) {
    89  					log.G(ctx).WithFields(log.Fields{
    90  						"configDigest": dgst,
    91  						"error":        err,
    92  					}).Warn("failed to read container config")
    93  				}
    94  				return nil
    95  			}
    96  
    97  			outImg.ContainerConfig = config
    98  
    99  			// We already have the config we looked for, so return an error to
   100  			// stop walking the image further. This error will be ignored.
   101  			return errFound
   102  		}
   103  		return nil
   104  	})
   105  	if err != nil && err != errFound {
   106  		return nil, err
   107  	}
   108  
   109  	return outImg, nil
   110  }
   111  
   112  func (c cacheAdaptor) GetByRef(ctx context.Context, refOrId string) (*image.Image, error) {
   113  	return c.is.GetImage(ctx, refOrId, backend.GetImageOpts{})
   114  }
   115  
   116  func (c cacheAdaptor) SetParent(target, parent image.ID) error {
   117  	ctx := context.TODO()
   118  	_, imgs, err := c.is.resolveAllReferences(ctx, target.String())
   119  	if err != nil {
   120  		return fmt.Errorf("failed to list images with digest %q", target)
   121  	}
   122  
   123  	var errs []error
   124  	is := c.is.images
   125  	for _, img := range imgs {
   126  		if img.Labels == nil {
   127  			img.Labels = make(map[string]string)
   128  		}
   129  		img.Labels[imageLabelClassicBuilderParent] = parent.String()
   130  		if _, err := is.Update(ctx, img, "labels."+imageLabelClassicBuilderParent); err != nil {
   131  			errs = append(errs, fmt.Errorf("failed to update parent label on image %v: %w", img, err))
   132  		}
   133  	}
   134  
   135  	return multierror.Join(errs...)
   136  }
   137  
   138  func (c cacheAdaptor) GetParent(target image.ID) (image.ID, error) {
   139  	ctx := context.TODO()
   140  	value, err := c.is.getImageLabelByDigest(ctx, target.Digest(), imageLabelClassicBuilderParent)
   141  	if err != nil {
   142  		return "", fmt.Errorf("failed to read parent image: %w", err)
   143  	}
   144  
   145  	dgst, err := digest.Parse(value)
   146  	if err != nil {
   147  		return "", fmt.Errorf("invalid parent value: %q", value)
   148  	}
   149  
   150  	return image.ID(dgst), nil
   151  }
   152  
   153  func (c cacheAdaptor) Create(parent *image.Image, target image.Image, extraLayer layer.DiffID) (image.ID, error) {
   154  	ctx := context.TODO()
   155  	data, err := json.Marshal(target)
   156  	if err != nil {
   157  		return "", fmt.Errorf("failed to marshal image config: %w", err)
   158  	}
   159  
   160  	var layerDigest digest.Digest
   161  	if extraLayer != "" {
   162  		info, err := findContentByUncompressedDigest(ctx, c.is.client.ContentStore(), digest.Digest(extraLayer))
   163  		if err != nil {
   164  			return "", fmt.Errorf("failed to find content for diff ID %q: %w", extraLayer, err)
   165  		}
   166  		layerDigest = info.Digest
   167  	}
   168  
   169  	var parentRef string
   170  	if parent != nil {
   171  		parentRef = parent.ID().String()
   172  	}
   173  	img, err := c.is.CreateImage(ctx, data, parentRef, layerDigest)
   174  	if err != nil {
   175  		return "", fmt.Errorf("failed to created cached image: %w", err)
   176  	}
   177  
   178  	return image.ID(img.ImageID()), nil
   179  }
   180  
   181  func (c cacheAdaptor) IsBuiltLocally(target image.ID) (bool, error) {
   182  	ctx := context.TODO()
   183  	value, err := c.is.getImageLabelByDigest(ctx, target.Digest(), imageLabelClassicBuilderContainerConfig)
   184  	if err != nil {
   185  		return false, fmt.Errorf("failed to read container config label: %w", err)
   186  	}
   187  	return value != "", nil
   188  }
   189  
   190  func (c cacheAdaptor) Children(id image.ID) []image.ID {
   191  	ctx := context.TODO()
   192  
   193  	if id.String() == "" {
   194  		imgs, err := c.is.getImagesWithLabel(ctx, imageLabelClassicBuilderFromScratch, "1")
   195  		if err != nil {
   196  			log.G(ctx).WithError(err).Error("failed to get from scratch images")
   197  			return nil
   198  		}
   199  		return imgs
   200  	}
   201  
   202  	imgs, err := c.is.Children(ctx, id)
   203  	if err != nil {
   204  		log.G(ctx).WithError(err).Error("failed to get image children")
   205  		return nil
   206  	}
   207  
   208  	return imgs
   209  }
   210  
   211  func findContentByUncompressedDigest(ctx context.Context, cs content.Manager, uncompressed digest.Digest) (content.Info, error) {
   212  	var out content.Info
   213  
   214  	errStopWalk := errors.New("success")
   215  	err := cs.Walk(ctx, func(i content.Info) error {
   216  		out = i
   217  		return errStopWalk
   218  	}, `labels."containerd.io/uncompressed"==`+uncompressed.String())
   219  
   220  	if err != nil && err != errStopWalk {
   221  		return out, err
   222  	}
   223  	if out.Digest == "" {
   224  		return out, errdefs.NotFound(errors.New("no content matches this uncompressed digest"))
   225  	}
   226  	return out, nil
   227  }