Skip to content

Commit ec5084f

Browse files
committed
containerimage: force oci-mediatypes for annotations/attestations
The new annotations and attestations features both utilize annotations in the oci image format. Many registries allow setting these annotation fields on docker formats, however, notably, GCR will reject these objects and only allow them for OCI media types. To work around this, we enable oci-mediatypes when annotations that require OCI are enabled by the user, printing a warning to the user, similar to how we already do for stargz compression. Signed-off-by: Justin Chadwell <[email protected]>
1 parent d890852 commit ec5084f

File tree

5 files changed

+165
-15
lines changed

5 files changed

+165
-15
lines changed

client/client_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ func TestIntegration(t *testing.T) {
171171
testCallInfo,
172172
testPullWithLayerLimit,
173173
testExportAnnotations,
174+
testExportAnnotationsMediaTypes,
174175
testExportAttestations,
175176
)
176177
tests = append(tests, diffOpTestCases()...)
@@ -6306,6 +6307,125 @@ func testExportAnnotations(t *testing.T, sb integration.Sandbox) {
63066307
}
63076308
}
63086309

6310+
func testExportAnnotationsMediaTypes(t *testing.T, sb integration.Sandbox) {
6311+
requiresLinux(t)
6312+
c, err := New(sb.Context(), sb.Address())
6313+
require.NoError(t, err)
6314+
defer c.Close()
6315+
6316+
p := platforms.DefaultSpec()
6317+
ps := []ocispecs.Platform{p}
6318+
6319+
frontend := func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
6320+
res := gateway.NewResult()
6321+
expPlatforms := &exptypes.Platforms{
6322+
Platforms: make([]exptypes.Platform, len(ps)),
6323+
}
6324+
for i, p := range ps {
6325+
st := llb.Scratch().File(
6326+
llb.Mkfile("platform", 0600, []byte(platforms.Format(p))),
6327+
)
6328+
6329+
def, err := st.Marshal(ctx)
6330+
if err != nil {
6331+
return nil, err
6332+
}
6333+
6334+
r, err := c.Solve(ctx, gateway.SolveRequest{
6335+
Definition: def.ToPB(),
6336+
})
6337+
if err != nil {
6338+
return nil, err
6339+
}
6340+
6341+
ref, err := r.SingleRef()
6342+
if err != nil {
6343+
return nil, err
6344+
}
6345+
6346+
_, err = ref.ToState()
6347+
if err != nil {
6348+
return nil, err
6349+
}
6350+
6351+
k := platforms.Format(p)
6352+
res.AddRef(k, ref)
6353+
6354+
expPlatforms.Platforms[i] = exptypes.Platform{
6355+
ID: k,
6356+
Platform: p,
6357+
}
6358+
}
6359+
dt, err := json.Marshal(expPlatforms)
6360+
if err != nil {
6361+
return nil, err
6362+
}
6363+
res.AddMeta(exptypes.ExporterPlatformsKey, dt)
6364+
6365+
return res, nil
6366+
}
6367+
6368+
target := "testannotationsmedia:1"
6369+
_, err = c.Build(sb.Context(), SolveOpt{
6370+
Exports: []ExportEntry{
6371+
{
6372+
Type: ExporterImage,
6373+
Attrs: map[string]string{
6374+
"name": target,
6375+
"annotation-manifest.x": "y",
6376+
},
6377+
},
6378+
},
6379+
}, "", frontend, nil)
6380+
require.NoError(t, err)
6381+
6382+
target2 := "testannotationsmedia:2"
6383+
_, err = c.Build(sb.Context(), SolveOpt{
6384+
Exports: []ExportEntry{
6385+
{
6386+
Type: ExporterImage,
6387+
Attrs: map[string]string{
6388+
"name": target2,
6389+
"annotation-index.x": "y",
6390+
},
6391+
},
6392+
},
6393+
}, "", frontend, nil)
6394+
require.NoError(t, err)
6395+
6396+
ctx := namespaces.WithNamespace(sb.Context(), "buildkit")
6397+
cdAddress := sb.ContainerdAddress()
6398+
if cdAddress != "" {
6399+
client, err := newContainerd(cdAddress)
6400+
require.NoError(t, err)
6401+
defer client.Close()
6402+
6403+
// test annotation in manifest
6404+
img, err := client.GetImage(ctx, target)
6405+
require.NoError(t, err)
6406+
6407+
var index ocispecs.Index
6408+
indexBytes, err := content.ReadBlob(ctx, client.ContentStore(), img.Target())
6409+
require.NoError(t, err)
6410+
require.NoError(t, json.Unmarshal(indexBytes, &index))
6411+
require.Equal(t, images.MediaTypeDockerSchema2ManifestList, index.MediaType)
6412+
6413+
mfst, err := images.Manifest(ctx, client.ContentStore(), img.Target(), platforms.Only(p))
6414+
require.NoError(t, err)
6415+
require.Equal(t, "y", mfst.Annotations["x"])
6416+
6417+
// test annotation in index
6418+
img, err = client.GetImage(ctx, target2)
6419+
require.NoError(t, err)
6420+
6421+
indexBytes, err = content.ReadBlob(ctx, client.ContentStore(), img.Target())
6422+
require.NoError(t, err)
6423+
require.NoError(t, json.Unmarshal(indexBytes, &index))
6424+
require.Equal(t, ocispecs.MediaTypeImageIndex, index.MediaType)
6425+
require.Equal(t, "y", index.Annotations["x"])
6426+
}
6427+
}
6428+
63096429
func testExportAttestations(t *testing.T, sb integration.Sandbox) {
63106430
requiresLinux(t)
63116431
c, err := New(sb.Context(), sb.Address())
@@ -6443,6 +6563,7 @@ func testExportAttestations(t *testing.T, sb integration.Sandbox) {
64436563
atts := imgs.Filter("unknown/unknown")
64446564
require.Equal(t, len(ps), len(atts.Images))
64456565
for i, att := range atts.Images {
6566+
require.Equal(t, ocispecs.MediaTypeImageManifest, att.Desc.MediaType)
64466567
require.Equal(t, "unknown/unknown", platforms.Format(*att.Desc.Platform))
64476568
require.Equal(t, "unknown/unknown", att.Img.OS+"/"+att.Img.Architecture)
64486569
require.Equal(t, attestation.DockerAnnotationReferenceTypeDefault, att.Desc.Annotations[attestation.DockerAnnotationReferenceType])

exporter/containerimage/export.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,7 @@ func (e *imageExporter) Resolve(ctx context.Context, opt map[string]string) (exp
7878
RefCfg: cacheconfig.RefConfig{
7979
Compression: compression.New(compression.Default),
8080
},
81-
BuildInfo: true,
82-
Annotations: make(AnnotationsGroup),
81+
BuildInfo: true,
8382
},
8483
store: true,
8584
}
@@ -210,7 +209,7 @@ func (e *imageExporterInstance) Export(ctx context.Context, src *exporter.Source
210209
if err != nil {
211210
return nil, err
212211
}
213-
opts.Annotations = as.Merge(opts.Annotations)
212+
opts.AddAnnotations(as)
214213

215214
ctx, done, err := leaseutil.WithLease(ctx, e.opt.LeaseManager, leaseutil.MakeTemporary)
216215
if err != nil {

exporter/containerimage/opts.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ func (c *ImageCommitOpts) Load(opt map[string]string) (map[string]string, error)
4242
if err != nil {
4343
return nil, err
4444
}
45-
c.Annotations = as
4645
opt = toStringMap(optb)
4746

4847
for k, v := range opt {
@@ -91,14 +90,42 @@ func (c *ImageCommitOpts) Load(opt map[string]string) (map[string]string, error)
9190
}
9291
}
9392

94-
if esgz && !c.OCITypes {
95-
logrus.Warn("forcibly turning on oci-mediatype mode for estargz")
96-
c.OCITypes = true
93+
if esgz {
94+
c.EnableOCITypes("estargz")
9795
}
9896

97+
c.AddAnnotations(as)
98+
9999
return rest, nil
100100
}
101101

102+
func (c *ImageCommitOpts) AddAnnotations(annotations AnnotationsGroup) {
103+
if annotations == nil {
104+
return
105+
}
106+
if c.Annotations == nil {
107+
c.Annotations = AnnotationsGroup{}
108+
}
109+
c.Annotations = c.Annotations.Merge(annotations)
110+
for _, a := range annotations {
111+
if len(a.Index)+len(a.IndexDescriptor)+len(a.ManifestDescriptor) > 0 {
112+
c.EnableOCITypes("annotations")
113+
}
114+
}
115+
}
116+
117+
func (c *ImageCommitOpts) EnableOCITypes(reason string) {
118+
if !c.OCITypes {
119+
message := "forcibly turning on oci-mediatype mode"
120+
if reason != "" {
121+
message += " for " + reason
122+
}
123+
logrus.Warn(message)
124+
125+
c.OCITypes = true
126+
}
127+
}
128+
102129
func parseBool(dest *bool, key string, value string) error {
103130
b, err := strconv.ParseBool(value)
104131
if err != nil {

exporter/containerimage/writer.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,16 @@ func (ic *ImageWriter) Commit(ctx context.Context, inp *exporter.Source, session
9999
return mfstDesc, nil
100100
}
101101

102-
refCount := len(p.Platforms)
102+
attestCount := 0
103103
for _, attests := range inp.Attestations {
104-
refCount += len(attests)
104+
attestCount += len(attests)
105105
}
106-
if refCount != len(inp.Refs) {
107-
return nil, errors.Errorf("number of required refs does not match references %d %d", refCount, len(inp.Refs))
106+
if count := attestCount + len(p.Platforms); count != len(inp.Refs) {
107+
return nil, errors.Errorf("number of required refs does not match references %d %d", count, len(inp.Refs))
108+
}
109+
110+
if attestCount > 0 {
111+
opts.EnableOCITypes("attestations")
108112
}
109113

110114
refs := make([]cache.ImmutableRef, 0, len(inp.Refs))

exporter/oci/export.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,8 @@ func (e *imageExporter) Resolve(ctx context.Context, opt map[string]string) (exp
5757
RefCfg: cacheconfig.RefConfig{
5858
Compression: compression.New(compression.Default),
5959
},
60-
BuildInfo: true,
61-
OCITypes: e.opt.Variant == VariantOCI,
62-
Annotations: make(containerimage.AnnotationsGroup),
60+
BuildInfo: true,
61+
OCITypes: e.opt.Variant == VariantOCI,
6362
},
6463
}
6564

@@ -110,7 +109,7 @@ func (e *imageExporterInstance) Export(ctx context.Context, src *exporter.Source
110109
if err != nil {
111110
return nil, err
112111
}
113-
opts.Annotations = as.Merge(opts.Annotations)
112+
opts.AddAnnotations(as)
114113

115114
ctx, done, err := leaseutil.WithLease(ctx, e.opt.LeaseManager, leaseutil.MakeTemporary)
116115
if err != nil {

0 commit comments

Comments
 (0)