2018-05-07 21:24:28 +00:00
|
|
|
package remotecache
|
2017-10-12 22:04:33 +00:00
|
|
|
|
|
|
|
import (
|
2018-01-16 22:30:10 +00:00
|
|
|
"context"
|
2017-10-12 22:04:33 +00:00
|
|
|
"encoding/json"
|
2018-07-03 09:59:33 +00:00
|
|
|
"io"
|
2017-10-12 22:04:33 +00:00
|
|
|
|
|
|
|
"github.com/containerd/containerd/content"
|
2018-05-07 21:24:28 +00:00
|
|
|
v1 "github.com/moby/buildkit/cache/remotecache/v1"
|
2018-05-11 05:58:41 +00:00
|
|
|
"github.com/moby/buildkit/solver"
|
2018-04-13 21:10:59 +00:00
|
|
|
"github.com/moby/buildkit/worker"
|
2017-10-12 22:04:33 +00:00
|
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
2018-07-03 09:59:33 +00:00
|
|
|
// 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)
|
2017-10-12 22:04:33 +00:00
|
|
|
|
2018-07-03 09:59:33 +00:00
|
|
|
type Importer interface {
|
|
|
|
Resolve(ctx context.Context, desc ocispec.Descriptor, id string, w worker.Worker) (solver.CacheManager, error)
|
2017-10-12 22:04:33 +00:00
|
|
|
}
|
|
|
|
|
2018-07-03 09:59:33 +00:00
|
|
|
func NewImporter(provider content.Provider) Importer {
|
|
|
|
return &contentCacheImporter{provider: provider}
|
2017-10-12 22:04:33 +00:00
|
|
|
}
|
|
|
|
|
2018-07-03 09:59:33 +00:00
|
|
|
type contentCacheImporter struct {
|
|
|
|
provider content.Provider
|
2017-10-15 06:49:55 +00:00
|
|
|
}
|
|
|
|
|
2018-07-03 09:59:33 +00:00
|
|
|
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)
|
2017-10-13 00:53:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-10-12 22:04:33 +00:00
|
|
|
|
2017-10-13 00:53:28 +00:00
|
|
|
var mfst ocispec.Index
|
|
|
|
if err := json.Unmarshal(dt, &mfst); err != nil {
|
|
|
|
return nil, err
|
2017-10-12 22:04:33 +00:00
|
|
|
}
|
|
|
|
|
2018-04-13 21:10:59 +00:00
|
|
|
allLayers := v1.DescriptorProvider{}
|
2017-10-12 22:04:33 +00:00
|
|
|
|
2017-10-13 21:24:27 +00:00
|
|
|
var configDesc ocispec.Descriptor
|
|
|
|
|
2017-10-13 00:53:28 +00:00
|
|
|
for _, m := range mfst.Manifests {
|
2018-04-13 21:10:59 +00:00
|
|
|
if m.MediaType == v1.CacheConfigMediaTypeV0 {
|
2017-10-13 21:24:27 +00:00
|
|
|
configDesc = m
|
|
|
|
continue
|
2017-10-12 22:04:33 +00:00
|
|
|
}
|
2018-04-13 21:10:59 +00:00
|
|
|
allLayers[m.Digest] = v1.DescriptorProviderPair{
|
|
|
|
Descriptor: m,
|
2018-07-03 09:59:33 +00:00
|
|
|
Provider: ci.provider,
|
2018-04-13 21:10:59 +00:00
|
|
|
}
|
2017-10-13 21:24:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if configDesc.Digest == "" {
|
2018-07-03 09:59:33 +00:00
|
|
|
return nil, errors.Errorf("invalid build cache from %+v", desc)
|
2017-10-13 21:24:27 +00:00
|
|
|
}
|
|
|
|
|
2018-07-03 09:59:33 +00:00
|
|
|
dt, err = readBlob(ctx, ci.provider, configDesc)
|
2017-10-13 21:24:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-04-13 21:10:59 +00:00
|
|
|
cc := v1.NewCacheChains()
|
|
|
|
if err := v1.Parse(dt, allLayers, cc); err != nil {
|
2017-10-13 21:24:27 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-07-03 09:59:33 +00:00
|
|
|
keysStorage, resultStorage, err := v1.NewCacheKeyStorage(cc, w)
|
2017-10-13 00:53:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-10-12 22:04:33 +00:00
|
|
|
}
|
2018-07-03 09:59:33 +00:00
|
|
|
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
|
2017-10-15 05:39:28 +00:00
|
|
|
}
|