dotcloud/docker

View on GitHub
daemon/containerd/cache.go

Summary

Maintainability
A
3 hrs
Test Coverage
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)
    }

    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 !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
}