| package containerd | |
| import ( | |
| "context" | |
| "encoding/json" | |
| "errors" | |
| "fmt" | |
| "github.com/containerd/containerd/v2/core/content" | |
| cerrdefs "github.com/containerd/errdefs" | |
| "github.com/containerd/log" | |
| "github.com/moby/moby/api/types/container" | |
| "github.com/moby/moby/v2/daemon/builder" | |
| "github.com/moby/moby/v2/daemon/internal/image" | |
| "github.com/moby/moby/v2/daemon/internal/image/cache" | |
| "github.com/moby/moby/v2/daemon/internal/layer" | |
| "github.com/moby/moby/v2/daemon/internal/multierror" | |
| "github.com/moby/moby/v2/daemon/server/imagebackend" | |
| "github.com/moby/moby/v2/errdefs" | |
| "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(), imagebackend.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) | |
| } | |
| 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 := readJSON(ctx, c.is.content, configDesc, &config); err != nil { | |
| if !cerrdefs.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 && !errors.Is(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, imagebackend.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(), 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 && !errors.Is(err, errStopWalk) { | |
| return out, err | |
| } | |
| if out.Digest == "" { | |
| return out, errdefs.NotFound(errors.New("no content matches this uncompressed digest")) | |
| } | |
| return out, nil | |
| } |