From b8385c98e89f1f77c573909d889b875581114420 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Thu, 14 Mar 2019 14:27:18 -0700 Subject: [PATCH] builder-next: support for inline cache from local images Signed-off-by: Tonis Tiigi --- .../adapters/localinlinecache/inlinecache.go | 163 ++++++++++++++++++ builder/builder-next/controller.go | 4 +- 2 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 builder/builder-next/adapters/localinlinecache/inlinecache.go diff --git a/builder/builder-next/adapters/localinlinecache/inlinecache.go b/builder/builder-next/adapters/localinlinecache/inlinecache.go new file mode 100644 index 0000000000..9ed2d53382 --- /dev/null +++ b/builder/builder-next/adapters/localinlinecache/inlinecache.go @@ -0,0 +1,163 @@ +package localinlinecache + +import ( + "context" + "encoding/json" + "time" + + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/images" + distreference "github.com/docker/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/util/resolver" + "github.com/moby/buildkit/worker" + digest "github.com/opencontainers/go-digest" + specs "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, resolverOpt resolver.ResolveOptionsFunc, rs reference.Store, is imagestore.Store) remotecache.ResolveCacheImporterFunc { + + upstream := registryremotecache.ResolveCacheImporterFunc(sm, resolverOpt) + + return func(ctx context.Context, attrs map[string]string) (remotecache.Importer, specs.Descriptor, error) { + if dt, err := tryImportLocal(rs, is, attrs["ref"]); err == nil { + return newLocalImporter(dt), specs.Descriptor{}, nil + } + return upstream(ctx, 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, _ specs.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(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 := specs.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 specs.Descriptor) (content.ReaderAt, error) { + return nil, errors.Errorf("ReaderAt not implemented for empty provider") +} diff --git a/builder/builder-next/controller.go b/builder/builder-next/controller.go index 1cb740b01e..ef94c898d0 100644 --- a/builder/builder-next/controller.go +++ b/builder/builder-next/controller.go @@ -8,6 +8,7 @@ import ( "github.com/containerd/containerd/content/local" "github.com/docker/docker/api/types" "github.com/docker/docker/builder/builder-next/adapters/containerimage" + "github.com/docker/docker/builder/builder-next/adapters/localinlinecache" "github.com/docker/docker/builder/builder-next/adapters/snapshot" containerimageexp "github.com/docker/docker/builder/builder-next/exporter" "github.com/docker/docker/builder/builder-next/imagerefchecker" @@ -19,7 +20,6 @@ import ( "github.com/moby/buildkit/cache/metadata" "github.com/moby/buildkit/cache/remotecache" inlineremotecache "github.com/moby/buildkit/cache/remotecache/inline" - registryremotecache "github.com/moby/buildkit/cache/remotecache/registry" "github.com/moby/buildkit/client" "github.com/moby/buildkit/control" "github.com/moby/buildkit/frontend" @@ -175,7 +175,7 @@ func newController(rt http.RoundTripper, opt Opt) (*control.Controller, error) { Frontends: frontends, CacheKeyStorage: cacheStorage, ResolveCacheImporterFuncs: map[string]remotecache.ResolveCacheImporterFunc{ - "registry": registryremotecache.ResolveCacheImporterFunc(opt.SessionManager, opt.ResolverOpt), + "registry": localinlinecache.ResolveCacheImporterFunc(opt.SessionManager, opt.ResolverOpt, dist.ReferenceStore, dist.ImageStore), }, ResolveCacheExporterFuncs: map[string]remotecache.ResolveCacheExporterFunc{ "inline": inlineremotecache.ResolveCacheExporterFunc(),