dotcloud/docker

View on GitHub
builder/builder-next/adapters/localinlinecache/inlinecache.go

Summary

Maintainability
A
1 hr
Test Coverage
package localinlinecache

import (
    "context"
    "encoding/json"
    "time"

    "github.com/containerd/containerd/content"
    "github.com/containerd/containerd/images"
    "github.com/containerd/containerd/remotes/docker"
    distreference "github.com/distribution/reference"
    imagestore "github.com/docker/docker/image"
    "github.com/docker/docker/reference"
    "github.com/moby/buildkit/cache/remotecache"
    registryremotecache "github.com/moby/buildkit/cache/remotecache/registry"
    v1 "github.com/moby/buildkit/cache/remotecache/v1"
    "github.com/moby/buildkit/session"
    "github.com/moby/buildkit/solver"
    "github.com/moby/buildkit/worker"
    "github.com/opencontainers/go-digest"
    ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    "github.com/pkg/errors"
)

// ResolveCacheImporterFunc returns a resolver function for local inline cache
func ResolveCacheImporterFunc(sm *session.Manager, resolverFunc docker.RegistryHosts, cs content.Store, rs reference.Store, is imagestore.Store) remotecache.ResolveCacheImporterFunc {
    upstream := registryremotecache.ResolveCacheImporterFunc(sm, cs, resolverFunc)

    return func(ctx context.Context, group session.Group, attrs map[string]string) (remotecache.Importer, ocispec.Descriptor, error) {
        if dt, err := tryImportLocal(rs, is, attrs["ref"]); err == nil {
            return newLocalImporter(dt), ocispec.Descriptor{}, nil
        }
        return upstream(ctx, group, attrs)
    }
}

func tryImportLocal(rs reference.Store, is imagestore.Store, refStr string) ([]byte, error) {
    ref, err := distreference.ParseNormalizedNamed(refStr)
    if err != nil {
        return nil, err
    }
    dgst, err := rs.Get(ref)
    if err != nil {
        return nil, err
    }
    img, err := is.Get(imagestore.ID(dgst))
    if err != nil {
        return nil, err
    }

    return img.RawJSON(), nil
}

func newLocalImporter(dt []byte) remotecache.Importer {
    return &localImporter{dt: dt}
}

type localImporter struct {
    dt []byte
}

func (li *localImporter) Resolve(ctx context.Context, _ ocispec.Descriptor, id string, w worker.Worker) (solver.CacheManager, error) {
    cc := v1.NewCacheChains()
    if err := li.importInlineCache(ctx, li.dt, cc); err != nil {
        return nil, err
    }

    keysStorage, resultStorage, err := v1.NewCacheKeyStorage(cc, w)
    if err != nil {
        return nil, err
    }
    return solver.NewCacheManager(ctx, id, keysStorage, resultStorage), nil
}

func (li *localImporter) importInlineCache(ctx context.Context, dt []byte, cc solver.CacheExporterTarget) error {
    var img image

    if err := json.Unmarshal(dt, &img); err != nil {
        return err
    }

    if img.Cache == nil {
        return nil
    }

    var config v1.CacheConfig
    if err := json.Unmarshal(img.Cache, &config.Records); err != nil {
        return err
    }

    createdDates, createdMsg, err := parseCreatedLayerInfo(img)
    if err != nil {
        return err
    }

    layers := v1.DescriptorProvider{}
    for i, diffID := range img.Rootfs.DiffIDs {
        dgst := digest.Digest(diffID.String())
        desc := ocispec.Descriptor{
            Digest:      dgst,
            Size:        -1,
            MediaType:   images.MediaTypeDockerSchema2Layer,
            Annotations: map[string]string{},
        }
        if createdAt := createdDates[i]; createdAt != "" {
            desc.Annotations["buildkit/createdat"] = createdAt
        }
        if createdBy := createdMsg[i]; createdBy != "" {
            desc.Annotations["buildkit/description"] = createdBy
        }
        desc.Annotations["containerd.io/uncompressed"] = img.Rootfs.DiffIDs[i].String()
        layers[dgst] = v1.DescriptorProviderPair{
            Descriptor: desc,
            Provider:   &emptyProvider{},
        }
        config.Layers = append(config.Layers, v1.CacheLayer{
            Blob:        dgst,
            ParentIndex: i - 1,
        })
    }

    return v1.ParseConfig(config, layers, cc)
}

type image struct {
    Rootfs struct {
        DiffIDs []digest.Digest `json:"diff_ids"`
    } `json:"rootfs"`
    Cache   []byte `json:"moby.buildkit.cache.v0"`
    History []struct {
        Created    *time.Time `json:"created,omitempty"`
        CreatedBy  string     `json:"created_by,omitempty"`
        EmptyLayer bool       `json:"empty_layer,omitempty"`
    } `json:"history,omitempty"`
}

func parseCreatedLayerInfo(img image) ([]string, []string, error) {
    dates := make([]string, 0, len(img.Rootfs.DiffIDs))
    createdBy := make([]string, 0, len(img.Rootfs.DiffIDs))
    for _, h := range img.History {
        if !h.EmptyLayer {
            str := ""
            if h.Created != nil {
                dt, err := h.Created.MarshalText()
                if err != nil {
                    return nil, nil, err
                }
                str = string(dt)
            }
            dates = append(dates, str)
            createdBy = append(createdBy, h.CreatedBy)
        }
    }
    return dates, createdBy, nil
}

type emptyProvider struct{}

func (p *emptyProvider) ReaderAt(ctx context.Context, dec ocispec.Descriptor) (content.ReaderAt, error) {
    return nil, errors.Errorf("ReaderAt not implemented for empty provider")
}