Skip to content

Commit 29fd071

Browse files
committed
feedback changes for moby/buildkit moby#2251
Signed-off-by: Matt Kang <[email protected]>
1 parent 797156a commit 29fd071

File tree

5 files changed

+268
-100
lines changed

5 files changed

+268
-100
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ buildctl build ... \
388388
* `min`: only export layers for the resulting image
389389
* `max`: export all the layers of all intermediate steps
390390
* `ref=<ref>`: specify repository reference to store cache, e.g. `docker.io/user/image:tag`
391-
* `image-manifest=<true|false>`: whether to export cache manifest as an OCI-compatible image manifest rather than a manifest list/index (default: `false`)
391+
* `image-manifest=<true|false>`: whether to export cache manifest as an OCI-compatible image manifest rather than a manifest list/index (default: `false`, must be used with `oci-mediatypes=true`)
392392
* `oci-mediatypes=<true|false>`: whether to use OCI mediatypes in exported manifests (default: `true`, since BuildKit `v0.8`)
393393
* `compression=<uncompressed|gzip|estargz|zstd>`: choose compression type for layers newly created and cached, gzip is default value. estargz and zstd should be used with `oci-mediatypes=true`
394394
* `compression-level=<value>`: choose compression level for gzip, estargz (0-9) and zstd (0-22)
@@ -415,7 +415,7 @@ The directory layout conforms to OCI Image Spec v1.0.
415415
* `max`: export all the layers of all intermediate steps
416416
* `dest=<path>`: destination directory for cache exporter
417417
* `tag=<tag>`: specify custom tag of image to write to local index (default: `latest`)
418-
* `image-manifest=<true|false>`: whether to export cache manifest as an OCI-compatible image manifest rather than a manifest list/index (default: `false`)
418+
* `image-manifest=<true|false>`: whether to export cache manifest as an OCI-compatible image manifest rather than a manifest list/index (default: `false`, must be used with `oci-mediatypes=true`)
419419
* `oci-mediatypes=<true|false>`: whether to use OCI mediatypes in exported manifests (default `true`, since BuildKit `v0.8`)
420420
* `compression=<uncompressed|gzip|estargz|zstd>`: choose compression type for layers newly created and cached, gzip is default value. estargz and zstd should be used with `oci-mediatypes=true`.
421421
* `compression-level=<value>`: compression level for gzip, estargz (0-9) and zstd (0-22)

cache/remotecache/export.go

Lines changed: 119 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"github.com/moby/buildkit/util/progress"
1717
"github.com/moby/buildkit/util/progress/logs"
1818
digest "github.com/opencontainers/go-digest"
19-
specs "github.com/opencontainers/image-spec/specs-go"
19+
"github.com/opencontainers/image-spec/specs-go"
2020
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
2121
"github.com/pkg/errors"
2222
)
@@ -37,17 +37,127 @@ type Config struct {
3737
Compression compression.Config
3838
}
3939

40+
type CacheType int
41+
4042
const (
4143
// ExportResponseManifestDesc is a key for the map returned from Exporter.Finalize.
4244
// The map value is a JSON string of an OCI desciptor of a manifest.
4345
ExporterResponseManifestDesc = "cache.manifest"
4446
)
4547

48+
const (
49+
NotSet CacheType = iota
50+
ManifestList
51+
ImageManifest
52+
)
53+
54+
func (data CacheType) String() string {
55+
switch data {
56+
case ManifestList:
57+
return "Manifest List"
58+
case ImageManifest:
59+
return "Image Manifest"
60+
default:
61+
return "Not Set"
62+
}
63+
}
64+
4665
func NewExporter(ingester content.Ingester, ref string, oci bool, imageManifest bool, compressionConfig compression.Config) Exporter {
4766
cc := v1.NewCacheChains()
4867
return &contentCacheExporter{CacheExporterTarget: cc, chains: cc, ingester: ingester, oci: oci, imageManifest: imageManifest, ref: ref, comp: compressionConfig}
4968
}
5069

70+
type ExportableCache struct {
71+
// This cache describes two distinct styles of exportable cache, one is an Index (or Manifest List) of blobs,
72+
// or as an artifact using the OCI image manifest format.
73+
ExportedManifest ocispecs.Manifest
74+
ExportedIndex ocispecs.Index
75+
CacheType CacheType
76+
OCI bool
77+
}
78+
79+
func NewExportableCache(oci bool, imageManifest bool) (*ExportableCache, error) {
80+
var mediaType string
81+
82+
if imageManifest {
83+
mediaType = ocispecs.MediaTypeImageManifest
84+
if !oci {
85+
return nil, errors.Errorf("invalid configuration for remote cache")
86+
}
87+
} else {
88+
if oci {
89+
mediaType = ocispecs.MediaTypeImageIndex
90+
} else {
91+
mediaType = images.MediaTypeDockerSchema2ManifestList
92+
}
93+
}
94+
95+
cacheType := ManifestList
96+
if imageManifest {
97+
cacheType = ImageManifest
98+
}
99+
100+
schemaVersion := specs.Versioned{SchemaVersion: 2}
101+
switch cacheType {
102+
case ManifestList:
103+
return &ExportableCache{ExportedIndex: ocispecs.Index{
104+
MediaType: mediaType,
105+
Versioned: schemaVersion,
106+
},
107+
CacheType: cacheType,
108+
OCI: oci,
109+
}, nil
110+
case ImageManifest:
111+
return &ExportableCache{ExportedManifest: ocispecs.Manifest{
112+
MediaType: mediaType,
113+
Versioned: schemaVersion,
114+
},
115+
CacheType: cacheType,
116+
OCI: oci,
117+
}, nil
118+
default:
119+
return nil, errors.Errorf("exportable cache type not set")
120+
}
121+
}
122+
123+
func (ec *ExportableCache) MediaType() string {
124+
if ec.CacheType == ManifestList {
125+
return ec.ExportedIndex.MediaType
126+
}
127+
return ec.ExportedManifest.MediaType
128+
}
129+
130+
func (ec *ExportableCache) AddCacheBlob(blob ocispecs.Descriptor) {
131+
if ec.CacheType == ManifestList {
132+
ec.ExportedIndex.Manifests = append(ec.ExportedIndex.Manifests, blob)
133+
} else {
134+
ec.ExportedManifest.Layers = append(ec.ExportedManifest.Layers, blob)
135+
}
136+
}
137+
138+
func (ec *ExportableCache) FinalizeCache(ctx context.Context) {
139+
if ec.CacheType == ManifestList {
140+
ec.ExportedIndex.Manifests = compression.ConvertAllLayerMediaTypes(ctx, ec.OCI, ec.ExportedIndex.Manifests...)
141+
} else {
142+
ec.ExportedManifest.Layers = compression.ConvertAllLayerMediaTypes(ctx, ec.OCI, ec.ExportedManifest.Layers...)
143+
}
144+
}
145+
146+
func (ec *ExportableCache) SetConfig(config ocispecs.Descriptor) {
147+
if ec.CacheType == ManifestList {
148+
ec.ExportedIndex.Manifests = append(ec.ExportedIndex.Manifests, config)
149+
} else {
150+
ec.ExportedManifest.Config = config
151+
}
152+
}
153+
154+
func (ec *ExportableCache) MarshalJSON() ([]byte, error) {
155+
if ec.CacheType == ManifestList {
156+
return json.Marshal(ec.ExportedIndex)
157+
}
158+
return json.Marshal(ec.ExportedManifest)
159+
}
160+
51161
type contentCacheExporter struct {
52162
solver.CacheExporterTarget
53163
chains *v1.CacheChains
@@ -75,24 +185,9 @@ func (ce *contentCacheExporter) Finalize(ctx context.Context) (map[string]string
75185
return nil, err
76186
}
77187

78-
// own type because oci type can't be pushed and docker type doesn't have annotations
79-
type abstractManifest struct {
80-
specs.Versioned
81-
82-
MediaType string `json:"mediaType,omitempty"`
83-
Config *ocispecs.Descriptor `json:"config,omitempty"`
84-
// Manifests references platform specific manifests.
85-
Manifests []ocispecs.Descriptor `json:"manifests,omitempty"`
86-
Layers []ocispecs.Descriptor `json:"layers,omitempty"`
87-
}
88-
89-
var mfst abstractManifest
90-
mfst.SchemaVersion = 2
91-
mfst.MediaType = images.MediaTypeDockerSchema2ManifestList
92-
if ce.oci && !ce.imageManifest {
93-
mfst.MediaType = ocispecs.MediaTypeImageIndex
94-
} else if ce.imageManifest {
95-
mfst.MediaType = ocispecs.MediaTypeImageManifest
188+
cache, err := NewExportableCache(ce.oci, ce.imageManifest)
189+
if err != nil {
190+
return nil, err
96191
}
97192

98193
for _, l := range config.Layers {
@@ -105,16 +200,10 @@ func (ce *contentCacheExporter) Finalize(ctx context.Context) (map[string]string
105200
return nil, layerDone(errors.Wrap(err, "error writing layer blob"))
106201
}
107202
layerDone(nil)
108-
if ce.imageManifest {
109-
mfst.Layers = append(mfst.Layers, dgstPair.Descriptor)
110-
} else {
111-
mfst.Manifests = append(mfst.Manifests, dgstPair.Descriptor)
112-
}
203+
cache.AddCacheBlob(dgstPair.Descriptor)
113204
}
114205

115-
if !ce.imageManifest {
116-
mfst.Manifests = compression.ConvertAllLayerMediaTypes(ctx, ce.oci, mfst.Manifests...)
117-
}
206+
cache.FinalizeCache(ctx)
118207

119208
dt, err := json.Marshal(config)
120209
if err != nil {
@@ -132,13 +221,9 @@ func (ce *contentCacheExporter) Finalize(ctx context.Context) (map[string]string
132221
}
133222
configDone(nil)
134223

135-
if ce.imageManifest {
136-
mfst.Config = &desc
137-
} else {
138-
mfst.Manifests = append(mfst.Manifests, desc)
139-
}
224+
cache.SetConfig(desc)
140225

141-
dt, err = json.Marshal(mfst)
226+
dt, err = cache.MarshalJSON()
142227
if err != nil {
143228
return nil, errors.Wrap(err, "failed to marshal manifest")
144229
}
@@ -147,7 +232,7 @@ func (ce *contentCacheExporter) Finalize(ctx context.Context) (map[string]string
147232
desc = ocispecs.Descriptor{
148233
Digest: dgst,
149234
Size: int64(len(dt)),
150-
MediaType: mfst.MediaType,
235+
MediaType: cache.MediaType(),
151236
}
152237

153238
mfstLog := fmt.Sprintf("writing cache manifest %s", dgst)

cache/remotecache/import.go

Lines changed: 10 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -8,44 +8,21 @@ import (
88
"sync"
99
"time"
1010

11-
"github.com/moby/buildkit/util/progress"
12-
"github.com/opencontainers/image-spec/specs-go"
13-
1411
"github.com/containerd/containerd/content"
1512
"github.com/containerd/containerd/images"
1613
v1 "github.com/moby/buildkit/cache/remotecache/v1"
1714
"github.com/moby/buildkit/session"
1815
"github.com/moby/buildkit/solver"
1916
"github.com/moby/buildkit/util/bklog"
2017
"github.com/moby/buildkit/util/imageutil"
18+
"github.com/moby/buildkit/util/progress"
2119
"github.com/moby/buildkit/worker"
2220
digest "github.com/opencontainers/go-digest"
2321
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
2422
"github.com/pkg/errors"
2523
"golang.org/x/sync/errgroup"
2624
)
2725

28-
type ManifestType int
29-
30-
const (
31-
NotInferred ManifestType = iota
32-
ManifestList
33-
ImageManifest
34-
)
35-
36-
func (data ManifestType) String() string {
37-
switch data {
38-
case NotInferred:
39-
return "Not Inferred"
40-
case ManifestList:
41-
return "Manifest List"
42-
case ImageManifest:
43-
return "Image Manifest"
44-
default:
45-
return "Not Inferred"
46-
}
47-
}
48-
4926
// ResolveCacheImporterFunc returns importer and descriptor.
5027
type ResolveCacheImporterFunc func(ctx context.Context, g session.Group, attrs map[string]string) (Importer, ocispecs.Descriptor, error)
5128

@@ -72,7 +49,7 @@ func (ci *contentCacheImporter) Resolve(ctx context.Context, desc ocispecs.Descr
7249
return nil, err
7350
}
7451

75-
manifestType, err := inferManifestType(ctx, dt)
52+
manifestType, err := imageutil.DetectManifestBlobMediaType(dt)
7653
if err != nil {
7754
return nil, err
7855
}
@@ -83,7 +60,8 @@ func (ci *contentCacheImporter) Resolve(ctx context.Context, desc ocispecs.Descr
8360
allLayers := v1.DescriptorProvider{}
8461
var configDesc ocispecs.Descriptor
8562

86-
if manifestType == ManifestList {
63+
switch manifestType {
64+
case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex:
8765
var mfst ocispecs.Index
8866
if err := json.Unmarshal(dt, &mfst); err != nil {
8967
return nil, err
@@ -99,24 +77,23 @@ func (ci *contentCacheImporter) Resolve(ctx context.Context, desc ocispecs.Descr
9977
Provider: ci.provider,
10078
}
10179
}
102-
} else if manifestType == ImageManifest {
80+
case images.MediaTypeDockerSchema2Manifest, ocispecs.MediaTypeImageManifest:
10381
var mfst ocispecs.Manifest
10482
if err := json.Unmarshal(dt, &mfst); err != nil {
10583
return nil, err
10684
}
10785

86+
if mfst.Config.MediaType == v1.CacheConfigMediaTypeV0 {
87+
configDesc = mfst.Config
88+
}
10889
for _, m := range mfst.Layers {
109-
if m.MediaType == v1.CacheConfigMediaTypeV0 {
110-
configDesc = m
111-
continue
112-
}
11390
allLayers[m.Digest] = v1.DescriptorProviderPair{
11491
Descriptor: m,
11592
Provider: ci.provider,
11693
}
11794
}
118-
} else {
119-
err = errors.Wrapf(err, "Unsupported or uninferrable manifest type")
95+
default:
96+
err = errors.Wrapf(err, "unsupported or uninferrable manifest type")
12097
return nil, err
12198
}
12299

@@ -150,37 +127,6 @@ func (ci *contentCacheImporter) Resolve(ctx context.Context, desc ocispecs.Descr
150127
return solver.NewCacheManager(ctx, id, keysStorage, resultStorage), nil
151128
}
152129

153-
// extends support for "new"-style image-manifest style remote cache manifests and determining downstream
154-
// handling based on inference of document structure (is this a new or old cache manifest type?)
155-
func inferManifestType(ctx context.Context, dt []byte) (ManifestType, error) {
156-
// this is a loose schema superset of both OCI Index and Manifest in order to
157-
// be able to poke at the structure of the imported cache manifest
158-
type OpenManifest struct {
159-
specs.Versioned
160-
161-
MediaType string `json:"mediaType,omitempty"`
162-
Config map[string]interface{} `json:"config,omitempty"`
163-
// Manifests references platform specific manifests.
164-
Manifests []map[string]interface{} `json:"manifests,omitempty"`
165-
Layers []map[string]interface{} `json:"layers,omitempty"`
166-
}
167-
168-
var openManifest OpenManifest
169-
if err := json.Unmarshal(dt, &openManifest); err != nil {
170-
return NotInferred, err
171-
}
172-
173-
if len(openManifest.Manifests) == 0 && len(openManifest.Layers) > 0 {
174-
return ImageManifest, nil
175-
}
176-
177-
if len(openManifest.Layers) == 0 && len(openManifest.Manifests) > 0 {
178-
return ManifestList, nil
179-
}
180-
181-
return NotInferred, nil
182-
}
183-
184130
func readBlob(ctx context.Context, provider content.Provider, desc ocispecs.Descriptor) ([]byte, error) {
185131
maxBlobSize := int64(1 << 20)
186132
if desc.Size > maxBlobSize {

0 commit comments

Comments
 (0)