Skip to content

Commit 20b3cea

Browse files
committed
containerimage: keep layer labels for exported images
When moving blobs to history namespace also move the blobs the labels point to, but don't move actual image layers. Signed-off-by: Tonis Tiigi <[email protected]>
1 parent 62bdf96 commit 20b3cea

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,
@@ -384,6 +386,134 @@ func testHostNetworking(t *testing.T, sb integration.Sandbox) {
384386
}
385387
}
386388

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