| package remotecache |
| |
| import ( |
| "context" |
| "encoding/json" |
| "io" |
| |
| "github.com/containerd/containerd/content" |
| v1 "github.com/moby/buildkit/cache/remotecache/v1" |
| "github.com/moby/buildkit/solver" |
| "github.com/moby/buildkit/worker" |
| ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
| "github.com/pkg/errors" |
| ) |
| |
| // ResolveCacheImporterFunc returns importer and descriptor. |
| // Currently typ needs to be an empty string. |
| type ResolveCacheImporterFunc func(ctx context.Context, typ, ref string) (Importer, ocispec.Descriptor, error) |
| |
| type Importer interface { |
| Resolve(ctx context.Context, desc ocispec.Descriptor, id string, w worker.Worker) (solver.CacheManager, error) |
| } |
| |
| func NewImporter(provider content.Provider) Importer { |
| return &contentCacheImporter{provider: provider} |
| } |
| |
| type contentCacheImporter struct { |
| provider content.Provider |
| } |
| |
| func (ci *contentCacheImporter) Resolve(ctx context.Context, desc ocispec.Descriptor, id string, w worker.Worker) (solver.CacheManager, error) { |
| dt, err := readBlob(ctx, ci.provider, desc) |
| if err != nil { |
| return nil, err |
| } |
| |
| var mfst ocispec.Index |
| if err := json.Unmarshal(dt, &mfst); err != nil { |
| return nil, err |
| } |
| |
| allLayers := v1.DescriptorProvider{} |
| |
| var configDesc ocispec.Descriptor |
| |
| for _, m := range mfst.Manifests { |
| if m.MediaType == v1.CacheConfigMediaTypeV0 { |
| configDesc = m |
| continue |
| } |
| allLayers[m.Digest] = v1.DescriptorProviderPair{ |
| Descriptor: m, |
| Provider: ci.provider, |
| } |
| } |
| |
| if configDesc.Digest == "" { |
| return nil, errors.Errorf("invalid build cache from %+v", desc) |
| } |
| |
| dt, err = readBlob(ctx, ci.provider, configDesc) |
| if err != nil { |
| return nil, err |
| } |
| |
| cc := v1.NewCacheChains() |
| if err := v1.Parse(dt, allLayers, 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 readBlob(ctx context.Context, provider content.Provider, desc ocispec.Descriptor) ([]byte, error) { |
| maxBlobSize := int64(1 << 20) |
| if desc.Size > maxBlobSize { |
| return nil, errors.Errorf("blob %s is too large (%d > %d)", desc.Digest, desc.Size, maxBlobSize) |
| } |
| dt, err := content.ReadBlob(ctx, provider, desc) |
| if err != nil { |
| // NOTE: even if err == EOF, we might have got expected dt here. |
| // For instance, http.Response.Body is known to return non-zero bytes with EOF. |
| if err == io.EOF { |
| if dtDigest := desc.Digest.Algorithm().FromBytes(dt); dtDigest != desc.Digest { |
| err = errors.Wrapf(err, "got EOF, expected %s (%d bytes), got %s (%d bytes)", |
| desc.Digest, desc.Size, dtDigest, len(dt)) |
| } else { |
| err = nil |
| } |
| } |
| } |
| return dt, err |
| } |