| package containerd |
| |
| import ( |
| "context" |
| "encoding/json" |
| "errors" |
| "fmt" |
| |
| "github.com/containerd/containerd/content" |
| cerrdefs "github.com/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 |
| } |