Skip to content

Commit 402b1f8

Browse files
authored
Merge pull request moby#3972 from tonistiigi/fix-layer-labels
containerimage: keep layer labels for exported images
2 parents 87a136b + 20b3cea commit 402b1f8

File tree

3 files changed

+195
-12
lines changed

3 files changed

+195
-12
lines changed

client/client_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/containerd/containerd"
3030
"github.com/containerd/containerd/content"
3131
"github.com/containerd/containerd/content/local"
32+
"github.com/containerd/containerd/content/proxy"
3233
ctderrdefs "github.com/containerd/containerd/errdefs"
3334
"github.com/containerd/containerd/images"
3435
"github.com/containerd/containerd/namespaces"
@@ -182,6 +183,7 @@ func TestIntegration(t *testing.T) {
182183
testExportAnnotations,
183184
testExportAnnotationsMediaTypes,
184185
testExportAttestations,
186+
testExportedImageLabels,
185187
testAttestationDefaultSubject,
186188
testSourceDateEpochLayerTimestamps,
187189
testSourceDateEpochClamp,
@@ -385,6 +387,134 @@ func testHostNetworking(t *testing.T, sb integration.Sandbox) {
385387
}
386388
}
387389

390+
func testExportedImageLabels(t *testing.T, sb integration.Sandbox) {
391+
c, err := New(sb.Context(), sb.Address())
392+
require.NoError(t, err)
393+
defer c.Close()
394+
395+
cdAddress := sb.ContainerdAddress()
396+
if cdAddress == "" {
397+
t.Skip("only supported with containerd")
398+
}
399+
400+
ctx := sb.Context()
401+
402+
def, err := llb.Image("busybox").Run(llb.Shlexf("echo foo > /foo")).Marshal(ctx)
403+
require.NoError(t, err)
404+
405+
target := "docker.io/buildkit/build/exporter:labels"
406+
407+
_, err = c.Solve(ctx, def, SolveOpt{
408+
Exports: []ExportEntry{
409+
{
410+
Type: ExporterImage,
411+
Attrs: map[string]string{
412+
"name": target,
413+
},
414+
},
415+
},
416+
}, nil)
417+
require.NoError(t, err)
418+
419+
client, err := newContainerd(cdAddress)
420+
require.NoError(t, err)
421+
defer client.Close()
422+
423+
ctx = namespaces.WithNamespace(ctx, "buildkit")
424+
425+
img, err := client.GetImage(ctx, target)
426+
require.NoError(t, err)
427+
428+
store := client.ContentStore()
429+
430+
info, err := store.Info(ctx, img.Target().Digest)
431+
require.NoError(t, err)
432+
433+
dt, err := content.ReadBlob(ctx, store, img.Target())
434+
require.NoError(t, err)
435+
436+
var mfst ocispecs.Manifest
437+
err = json.Unmarshal(dt, &mfst)
438+
require.NoError(t, err)
439+
440+
require.Equal(t, 2, len(mfst.Layers))
441+
442+
hasLabel := func(dgst digest.Digest) bool {
443+
for k, v := range info.Labels {
444+
if strings.HasPrefix(k, "containerd.io/gc.ref.content.") && v == dgst.String() {
445+
return true
446+
}
447+
}
448+
return false
449+
}
450+
451+
// check that labels are set on all layers and config
452+
for _, l := range mfst.Layers {
453+
require.True(t, hasLabel(l.Digest))
454+
}
455+
require.True(t, hasLabel(mfst.Config.Digest))
456+
457+
err = c.Prune(sb.Context(), nil, PruneAll)
458+
require.NoError(t, err)
459+
460+
// layer should not be deleted
461+
_, err = store.Info(ctx, mfst.Layers[1].Digest)
462+
require.NoError(t, err)
463+
464+
err = client.ImageService().Delete(ctx, target, images.SynchronousDelete())
465+
require.NoError(t, err)
466+
467+
// layers should be deleted
468+
_, err = store.Info(ctx, mfst.Layers[1].Digest)
469+
require.Error(t, err)
470+
require.True(t, errors.Is(err, ctderrdefs.ErrNotFound))
471+
472+
// config should be deleted
473+
_, err = store.Info(ctx, mfst.Config.Digest)
474+
require.Error(t, err)
475+
require.True(t, errors.Is(err, ctderrdefs.ErrNotFound))
476+
477+
// buildkit contentstore still has the layer because it is multi-ns
478+
bkstore := proxy.NewContentStore(c.ContentClient())
479+
480+
// layer should be deleted as not kept by history
481+
_, err = bkstore.Info(ctx, mfst.Layers[1].Digest)
482+
require.Error(t, err)
483+
require.Contains(t, err.Error(), "not found")
484+
485+
// config should still be there
486+
_, err = bkstore.Info(ctx, img.Metadata().Target.Digest)
487+
require.NoError(t, err)
488+
489+
_, err = bkstore.Info(ctx, mfst.Config.Digest)
490+
require.NoError(t, err)
491+
492+
cl, err := c.ControlClient().ListenBuildHistory(sb.Context(), &controlapi.BuildHistoryRequest{
493+
EarlyExit: true,
494+
})
495+
require.NoError(t, err)
496+
497+
for {
498+
resp, err := cl.Recv()
499+
if err == io.EOF {
500+
break
501+
}
502+
require.NoError(t, err)
503+
_, err = c.ControlClient().UpdateBuildHistory(sb.Context(), &controlapi.UpdateBuildHistoryRequest{
504+
Ref: resp.Record.Ref,
505+
Delete: true,
506+
})
507+
require.NoError(t, err)
508+
}
509+
510+
// now everything should be deleted
511+
_, err = bkstore.Info(ctx, img.Metadata().Target.Digest)
512+
require.Error(t, err)
513+
514+
_, err = bkstore.Info(ctx, mfst.Config.Digest)
515+
require.Error(t, err)
516+
}
517+
388518
// #877
389519
func testExportBusyboxLocal(t *testing.T, sb integration.Sandbox) {
390520
c, err := New(sb.Context(), sb.Address())

exporter/containerimage/writer.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,9 +376,10 @@ func (ic *ImageWriter) commitDistributionManifest(ctx context.Context, opts *Ima
376376
"containerd.io/gc.ref.content.0": configDigest.String(),
377377
}
378378

379-
for _, desc := range remote.Descriptors {
379+
for i, desc := range remote.Descriptors {
380380
desc.Annotations = RemoveInternalLayerAnnotations(desc.Annotations, opts.OCITypes)
381381
mfst.Layers = append(mfst.Layers, desc)
382+
labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i+1)] = desc.Digest.String()
382383
}
383384

384385
mfstJSON, err := json.MarshalIndent(mfst, "", " ")

solver/llbsolver/history.go

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import (
44
"bufio"
55
"context"
66
"encoding/binary"
7+
"encoding/json"
78
"io"
89
"os"
910
"sort"
1011
"strconv"
12+
"strings"
1113
"sync"
1214
"time"
1315

@@ -142,7 +144,7 @@ func (h *HistoryQueue) migrateV2() error {
142144
recs2 := make([]leases.Resource, 0, len(recs))
143145
for _, r := range recs {
144146
if r.Type == "content" {
145-
if ok, err := h.migrateBlobV2(ctx, r.ID); err != nil {
147+
if ok, err := h.migrateBlobV2(ctx, r.ID, false); err != nil {
146148
return err
147149
} else if ok {
148150
recs2 = append(recs2, r)
@@ -185,11 +187,56 @@ func (h *HistoryQueue) migrateV2() error {
185187
return nil
186188
}
187189

188-
func (h *HistoryQueue) migrateBlobV2(ctx context.Context, id string) (bool, error) {
190+
func (h *HistoryQueue) blobRefs(ctx context.Context, dgst digest.Digest, detectSkipLayer bool) ([]digest.Digest, error) {
191+
info, err := h.opt.ContentStore.Info(ctx, dgst)
192+
if err != nil {
193+
return nil, err // allow missing blobs
194+
}
195+
var out []digest.Digest
196+
layers := map[digest.Digest]struct{}{}
197+
if detectSkipLayer {
198+
dt, err := content.ReadBlob(ctx, h.opt.ContentStore, ocispecs.Descriptor{
199+
Digest: dgst,
200+
})
201+
if err != nil {
202+
return nil, err
203+
}
204+
var mfst ocispecs.Manifest
205+
if err := json.Unmarshal(dt, &mfst); err != nil {
206+
return nil, err
207+
}
208+
for _, l := range mfst.Layers {
209+
layers[l.Digest] = struct{}{}
210+
}
211+
}
212+
for k, v := range info.Labels {
213+
if !strings.HasPrefix(k, "containerd.io/gc.ref.content.") {
214+
continue
215+
}
216+
dgst, err := digest.Parse(v)
217+
if err != nil {
218+
continue
219+
}
220+
if _, ok := layers[dgst]; ok {
221+
continue
222+
}
223+
out = append(out, dgst)
224+
}
225+
return out, nil
226+
}
227+
228+
func (h *HistoryQueue) migrateBlobV2(ctx context.Context, id string, detectSkipLayers bool) (bool, error) {
189229
dgst, err := digest.Parse(id)
190230
if err != nil {
191231
return false, err
192232
}
233+
234+
refs, _ := h.blobRefs(ctx, dgst, detectSkipLayers) // allow missing blobs
235+
labels := map[string]string{}
236+
for i, r := range refs {
237+
labels["containerd.io/gc.ref.content."+strconv.Itoa(i)] = r.String()
238+
}
239+
193240
w, err := content.OpenWriter(ctx, h.hContentStore, content.WithDescriptor(ocispecs.Descriptor{
194241
Digest: dgst,
195242
}), content.WithRef("history-migrate-"+id))
@@ -207,9 +254,14 @@ func (h *HistoryQueue) migrateBlobV2(ctx context.Context, id string) (bool, erro
207254
return false, nil // allow skipping
208255
}
209256
defer ra.Close()
210-
if err := content.Copy(ctx, w, &reader{ReaderAt: ra}, 0, dgst); err != nil {
257+
if err := content.Copy(ctx, w, &reader{ReaderAt: ra}, 0, dgst, content.WithLabels(labels)); err != nil {
211258
return false, err
212259
}
260+
261+
for _, refs := range refs {
262+
h.migrateBlobV2(ctx, refs.String(), detectSkipLayers) // allow missing blobs
263+
}
264+
213265
return true, nil
214266
}
215267

@@ -307,7 +359,7 @@ func (h *HistoryQueue) leaseID(id string) string {
307359
return "ref_" + id
308360
}
309361

310-
func (h *HistoryQueue) addResource(ctx context.Context, l leases.Lease, desc *controlapi.Descriptor) error {
362+
func (h *HistoryQueue) addResource(ctx context.Context, l leases.Lease, desc *controlapi.Descriptor, detectSkipLayers bool) error {
311363
if desc == nil {
312364
return nil
313365
}
@@ -318,7 +370,7 @@ func (h *HistoryQueue) addResource(ctx context.Context, l leases.Lease, desc *co
318370
return err
319371
}
320372
defer release(ctx)
321-
ok, err := h.migrateBlobV2(ctx, string(desc.Digest))
373+
ok, err := h.migrateBlobV2(ctx, string(desc.Digest), detectSkipLayers)
322374
if err != nil {
323375
return err
324376
}
@@ -467,28 +519,28 @@ func (h *HistoryQueue) update(ctx context.Context, rec controlapi.BuildHistoryRe
467519
}
468520
}()
469521

470-
if err := h.addResource(ctx, l, rec.Logs); err != nil {
522+
if err := h.addResource(ctx, l, rec.Logs, false); err != nil {
471523
return err
472524
}
473-
if err := h.addResource(ctx, l, rec.Trace); err != nil {
525+
if err := h.addResource(ctx, l, rec.Trace, false); err != nil {
474526
return err
475527
}
476528
if rec.Result != nil {
477-
if err := h.addResource(ctx, l, rec.Result.Result); err != nil {
529+
if err := h.addResource(ctx, l, rec.Result.Result, true); err != nil {
478530
return err
479531
}
480532
for _, att := range rec.Result.Attestations {
481-
if err := h.addResource(ctx, l, att); err != nil {
533+
if err := h.addResource(ctx, l, att, false); err != nil {
482534
return err
483535
}
484536
}
485537
}
486538
for _, r := range rec.Results {
487-
if err := h.addResource(ctx, l, r.Result); err != nil {
539+
if err := h.addResource(ctx, l, r.Result, true); err != nil {
488540
return err
489541
}
490542
for _, att := range r.Attestations {
491-
if err := h.addResource(ctx, l, att); err != nil {
543+
if err := h.addResource(ctx, l, att, false); err != nil {
492544
return err
493545
}
494546
}

0 commit comments

Comments
 (0)