package remotecache import ( "bytes" "context" "encoding/json" "fmt" "time" "github.com/containerd/containerd/content" "github.com/containerd/containerd/images" v1 "github.com/moby/buildkit/cache/remotecache/v1" "github.com/moby/buildkit/solver" "github.com/moby/buildkit/util/contentutil" "github.com/moby/buildkit/util/progress" digest "github.com/opencontainers/go-digest" specs "github.com/opencontainers/image-spec/specs-go" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) type ResolveCacheExporterFunc func(ctx context.Context, typ, target string) (Exporter, error) func oneOffProgress(ctx context.Context, id string) func(err error) error { pw, _, _ := progress.FromContext(ctx) now := time.Now() st := progress.Status{ Started: &now, } pw.Write(id, st) return func(err error) error { now := time.Now() st.Completed = &now pw.Write(id, st) pw.Close() return err } } type Exporter interface { solver.CacheExporterTarget Finalize(ctx context.Context) error } type contentCacheExporter struct { solver.CacheExporterTarget chains *v1.CacheChains ingester content.Ingester } func NewExporter(ingester content.Ingester) Exporter { cc := v1.NewCacheChains() return &contentCacheExporter{CacheExporterTarget: cc, chains: cc, ingester: ingester} } func (ce *contentCacheExporter) Finalize(ctx context.Context) error { return export(ctx, ce.ingester, ce.chains) } func export(ctx context.Context, ingester content.Ingester, cc *v1.CacheChains) error { config, descs, err := cc.Marshal() if err != nil { return err } // own type because oci type can't be pushed and docker type doesn't have annotations type manifestList struct { specs.Versioned MediaType string `json:"mediaType,omitempty"` // Manifests references platform specific manifests. Manifests []ocispec.Descriptor `json:"manifests"` } var mfst manifestList mfst.SchemaVersion = 2 mfst.MediaType = images.MediaTypeDockerSchema2ManifestList for _, l := range config.Layers { dgstPair, ok := descs[l.Blob] if !ok { return errors.Errorf("missing blob %s", l.Blob) } layerDone := oneOffProgress(ctx, fmt.Sprintf("writing layer %s", l.Blob)) if err := contentutil.Copy(ctx, ingester, dgstPair.Provider, dgstPair.Descriptor); err != nil { return layerDone(errors.Wrap(err, "error writing layer blob")) } layerDone(nil) mfst.Manifests = append(mfst.Manifests, dgstPair.Descriptor) } dt, err := json.Marshal(config) if err != nil { return err } dgst := digest.FromBytes(dt) desc := ocispec.Descriptor{ Digest: dgst, Size: int64(len(dt)), MediaType: v1.CacheConfigMediaTypeV0, } configDone := oneOffProgress(ctx, fmt.Sprintf("writing config %s", dgst)) if err := content.WriteBlob(ctx, ingester, dgst.String(), bytes.NewReader(dt), desc); err != nil { return configDone(errors.Wrap(err, "error writing config blob")) } configDone(nil) mfst.Manifests = append(mfst.Manifests, desc) dt, err = json.Marshal(mfst) if err != nil { return errors.Wrap(err, "failed to marshal manifest") } dgst = digest.FromBytes(dt) desc = ocispec.Descriptor{ Digest: dgst, Size: int64(len(dt)), MediaType: mfst.MediaType, } mfstDone := oneOffProgress(ctx, fmt.Sprintf("writing manifest %s", dgst)) if err := content.WriteBlob(ctx, ingester, dgst.String(), bytes.NewReader(dt), desc); err != nil { return mfstDone(errors.Wrap(err, "error writing manifest blob")) } mfstDone(nil) return nil }