e8496b1ee4
Both containerd and graphdriver image service use the same code to create the cache - they only supply their own `cacheAdaptor` struct. Extract the shared code to `cache.New`. Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
227 lines
6.1 KiB
Go
227 lines
6.1 KiB
Go
package containerd
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/containerd/containerd/content"
|
|
cerrdefs "github.com/containerd/containerd/errdefs"
|
|
"github.com/containerd/log"
|
|
"github.com/docker/docker/api/types/backend"
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/builder"
|
|
"github.com/docker/docker/errdefs"
|
|
"github.com/docker/docker/image"
|
|
"github.com/docker/docker/image/cache"
|
|
"github.com/docker/docker/internal/multierror"
|
|
"github.com/docker/docker/layer"
|
|
"github.com/opencontainers/go-digest"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
)
|
|
|
|
// MakeImageCache creates a stateful image cache.
|
|
func (i *ImageService) MakeImageCache(ctx context.Context, sourceRefs []string) (builder.ImageCache, error) {
|
|
return cache.New(ctx, cacheAdaptor{i}, sourceRefs)
|
|
}
|
|
|
|
type cacheAdaptor struct {
|
|
is *ImageService
|
|
}
|
|
|
|
func (c cacheAdaptor) Get(id image.ID) (*image.Image, error) {
|
|
ctx := context.TODO()
|
|
ref := id.String()
|
|
|
|
outImg, err := c.is.GetImage(ctx, id.String(), backend.GetImageOpts{})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("GetImage: %w", err)
|
|
}
|
|
|
|
c8dImg, err := c.is.resolveImage(ctx, ref)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("resolveImage: %w", err)
|
|
}
|
|
|
|
var errFound = errors.New("success")
|
|
err = c.is.walkImageManifests(ctx, c8dImg, func(img *ImageManifest) error {
|
|
desc, err := img.Config(ctx)
|
|
if err != nil {
|
|
log.G(ctx).WithFields(log.Fields{
|
|
"image": img,
|
|
"error": err,
|
|
}).Warn("failed to get config descriptor for image")
|
|
return nil
|
|
}
|
|
|
|
info, err := c.is.content.Info(ctx, desc.Digest)
|
|
if err != nil {
|
|
if !cerrdefs.IsNotFound(err) {
|
|
log.G(ctx).WithFields(log.Fields{
|
|
"image": img,
|
|
"desc": desc,
|
|
"error": err,
|
|
}).Warn("failed to get info of image config")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if dgstStr, ok := info.Labels[contentLabelGcRefContainerConfig]; ok {
|
|
dgst, err := digest.Parse(dgstStr)
|
|
if err != nil {
|
|
log.G(ctx).WithFields(log.Fields{
|
|
"label": contentLabelClassicBuilderImage,
|
|
"value": dgstStr,
|
|
"content": desc.Digest,
|
|
"error": err,
|
|
}).Warn("invalid digest in label")
|
|
return nil
|
|
}
|
|
|
|
configDesc := ocispec.Descriptor{
|
|
Digest: dgst,
|
|
}
|
|
|
|
var config container.Config
|
|
if err := readConfig(ctx, c.is.content, configDesc, &config); err != nil {
|
|
if !errdefs.IsNotFound(err) {
|
|
log.G(ctx).WithFields(log.Fields{
|
|
"configDigest": dgst,
|
|
"error": err,
|
|
}).Warn("failed to read container config")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
outImg.ContainerConfig = config
|
|
|
|
// We already have the config we looked for, so return an error to
|
|
// stop walking the image further. This error will be ignored.
|
|
return errFound
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil && err != errFound {
|
|
return nil, err
|
|
}
|
|
|
|
return outImg, nil
|
|
}
|
|
|
|
func (c cacheAdaptor) GetByRef(ctx context.Context, refOrId string) (*image.Image, error) {
|
|
return c.is.GetImage(ctx, refOrId, backend.GetImageOpts{})
|
|
}
|
|
|
|
func (c cacheAdaptor) SetParent(target, parent image.ID) error {
|
|
ctx := context.TODO()
|
|
_, imgs, err := c.is.resolveAllReferences(ctx, target.String())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list images with digest %q", target)
|
|
}
|
|
|
|
var errs []error
|
|
is := c.is.images
|
|
for _, img := range imgs {
|
|
if img.Labels == nil {
|
|
img.Labels = make(map[string]string)
|
|
}
|
|
img.Labels[imageLabelClassicBuilderParent] = parent.String()
|
|
if _, err := is.Update(ctx, img, "labels."+imageLabelClassicBuilderParent); err != nil {
|
|
errs = append(errs, fmt.Errorf("failed to update parent label on image %v: %w", img, err))
|
|
}
|
|
}
|
|
|
|
return multierror.Join(errs...)
|
|
}
|
|
|
|
func (c cacheAdaptor) GetParent(target image.ID) (image.ID, error) {
|
|
ctx := context.TODO()
|
|
value, err := c.is.getImageLabelByDigest(ctx, target.Digest(), imageLabelClassicBuilderParent)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read parent image: %w", err)
|
|
}
|
|
|
|
dgst, err := digest.Parse(value)
|
|
if err != nil {
|
|
return "", fmt.Errorf("invalid parent value: %q", value)
|
|
}
|
|
|
|
return image.ID(dgst), nil
|
|
}
|
|
|
|
func (c cacheAdaptor) Create(parent *image.Image, target image.Image, extraLayer layer.DiffID) (image.ID, error) {
|
|
ctx := context.TODO()
|
|
data, err := json.Marshal(target)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to marshal image config: %w", err)
|
|
}
|
|
|
|
var layerDigest digest.Digest
|
|
if extraLayer != "" {
|
|
info, err := findContentByUncompressedDigest(ctx, c.is.client.ContentStore(), digest.Digest(extraLayer))
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to find content for diff ID %q: %w", extraLayer, err)
|
|
}
|
|
layerDigest = info.Digest
|
|
}
|
|
|
|
var parentRef string
|
|
if parent != nil {
|
|
parentRef = parent.ID().String()
|
|
}
|
|
img, err := c.is.CreateImage(ctx, data, parentRef, layerDigest)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to created cached image: %w", err)
|
|
}
|
|
|
|
return image.ID(img.ImageID()), nil
|
|
}
|
|
|
|
func (c cacheAdaptor) IsBuiltLocally(target image.ID) (bool, error) {
|
|
ctx := context.TODO()
|
|
value, err := c.is.getImageLabelByDigest(ctx, target.Digest(), imageLabelClassicBuilderContainerConfig)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to read container config label: %w", err)
|
|
}
|
|
return value != "", nil
|
|
}
|
|
|
|
func (c cacheAdaptor) Children(id image.ID) []image.ID {
|
|
ctx := context.TODO()
|
|
|
|
if id.String() == "" {
|
|
imgs, err := c.is.getImagesWithLabel(ctx, imageLabelClassicBuilderFromScratch, "1")
|
|
if err != nil {
|
|
log.G(ctx).WithError(err).Error("failed to get from scratch images")
|
|
return nil
|
|
}
|
|
return imgs
|
|
}
|
|
|
|
imgs, err := c.is.Children(ctx, id)
|
|
if err != nil {
|
|
log.G(ctx).WithError(err).Error("failed to get image children")
|
|
return nil
|
|
}
|
|
|
|
return imgs
|
|
}
|
|
|
|
func findContentByUncompressedDigest(ctx context.Context, cs content.Manager, uncompressed digest.Digest) (content.Info, error) {
|
|
var out content.Info
|
|
|
|
errStopWalk := errors.New("success")
|
|
err := cs.Walk(ctx, func(i content.Info) error {
|
|
out = i
|
|
return errStopWalk
|
|
}, `labels."containerd.io/uncompressed"==`+uncompressed.String())
|
|
|
|
if err != nil && err != errStopWalk {
|
|
return out, err
|
|
}
|
|
if out.Digest == "" {
|
|
return out, errdefs.NotFound(errors.New("no content matches this uncompressed digest"))
|
|
}
|
|
return out, nil
|
|
}
|