| 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/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/worker" |
| digest "github.com/opencontainers/go-digest" |
| specs "github.com/opencontainers/image-spec/specs-go/v1" |
| "github.com/pkg/errors" |
| ) |
| |
| func init() { |
| // See https://github.com/moby/buildkit/pull/1993. |
| v1.EmptyLayerRemovalSupported = false |
| } |
| |
| // 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, specs.Descriptor, error) { |
| if dt, err := tryImportLocal(rs, is, attrs["ref"]); err == nil { |
| return newLocalImporter(dt), specs.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, _ 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") |
| } |