Skip to content

Commit c8b14ae

Browse files
committed
Set content labels based on content type
Give control of the content labeling process for children to the client. This allows the client to control the names associated with the labels and filter out labels. Signed-off-by: Derek McGowan <derek@mcg.dev>
1 parent 03ab1b2 commit c8b14ae

File tree

7 files changed

+86
-54
lines changed

7 files changed

+86
-54
lines changed

client.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -312,11 +312,6 @@ type RemoteContext struct {
312312
// afterwards. Unpacking is required to run an image.
313313
Unpack bool
314314

315-
// DiscardContent is a boolean flag to specify whether to allow GC to clean
316-
// layers up from the content store after successfully unpacking these
317-
// contents to the snapshotter.
318-
DiscardContent bool
319-
320315
// UnpackOpts handles options to the unpack call.
321316
UnpackOpts []UnpackOpt
322317

@@ -356,6 +351,10 @@ type RemoteContext struct {
356351

357352
// AllMetadata downloads all manifests and known-configuration files
358353
AllMetadata bool
354+
355+
// ChildLabelMap sets the labels used to reference child objects in the content
356+
// store. By default, all GC reference labels will be set for all fetched content.
357+
ChildLabelMap func(ocispec.Descriptor) []string
359358
}
360359

361360
func defaultRemoteContext() *RemoteContext {

client_opts.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/containerd/containerd/platforms"
2424
"github.com/containerd/containerd/remotes"
2525
"github.com/containerd/containerd/snapshots"
26+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
2627

2728
"google.golang.org/grpc"
2829
)
@@ -132,14 +133,6 @@ func WithPullUnpack(_ *Client, c *RemoteContext) error {
132133
return nil
133134
}
134135

135-
// WithDiscardContent is used to allow GC to clean layers up from
136-
// the content store after successfully unpacking these contents to
137-
// the snapshotter.
138-
func WithDiscardContent(_ *Client, c *RemoteContext) error {
139-
c.DiscardContent = true
140-
return nil
141-
}
142-
143136
// WithUnpackOpts is used to add unpack options to the unpacker.
144137
func WithUnpackOpts(opts []UnpackOpt) RemoteOpt {
145138
return func(_ *Client, c *RemoteContext) error {
@@ -183,6 +176,18 @@ func WithPullLabels(labels map[string]string) RemoteOpt {
183176
}
184177
}
185178

179+
// WithChildLabelMap sets the map function used to define the labels set
180+
// on referenced child content in the content store. This can be used
181+
// to overwrite the default GC labels or filter which labels get set
182+
// for content.
183+
// The default is `images.ChildGCLabels`.
184+
func WithChildLabelMap(fn func(ocispec.Descriptor) []string) RemoteOpt {
185+
return func(_ *Client, c *RemoteContext) error {
186+
c.ChildLabelMap = fn
187+
return nil
188+
}
189+
}
190+
186191
// WithSchema1Conversion is used to convert Docker registry schema 1
187192
// manifests to oci manifests on pull. Without this option schema 1
188193
// manifests will return a not supported error.

client_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,11 @@ func TestImagePullWithDiscardContent(t *testing.T) {
229229
ctx, cancel := testContext(t)
230230
defer cancel()
231231

232+
err = client.ImageService().Delete(ctx, testImage, images.SynchronousDelete())
233+
if err != nil {
234+
t.Fatal(err)
235+
}
236+
232237
ls := client.LeasesService()
233238
l, err := ls.Create(ctx, leases.WithRandomID(), leases.WithExpiration(24*time.Hour))
234239
if err != nil {
@@ -238,7 +243,7 @@ func TestImagePullWithDiscardContent(t *testing.T) {
238243
img, err := client.Pull(ctx, testImage,
239244
WithPlatformMatcher(platforms.Default()),
240245
WithPullUnpack,
241-
WithDiscardContent,
246+
WithChildLabelMap(images.ChildGCLabelsFilterLayers),
242247
)
243248
// Synchronously garbage collect contents
244249
if errL := ls.Delete(ctx, l, leases.SynchronousDelete); errL != nil {

images/handlers.go

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -170,21 +170,46 @@ func ChildrenHandler(provider content.Provider) HandlerFunc {
170170
// the children returned by the handler and passes through the children.
171171
// Must follow a handler that returns the children to be labeled.
172172
func SetChildrenLabels(manager content.Manager, f HandlerFunc) HandlerFunc {
173+
return SetChildrenMappedLabels(manager, f, nil)
174+
}
175+
176+
// SetChildrenMappedLabels is a handler wrapper which sets labels for the content on
177+
// the children returned by the handler and passes through the children.
178+
// Must follow a handler that returns the children to be labeled.
179+
// The label map allows the caller to control the labels per child descriptor.
180+
// For returned labels, the index of the child will be appended to the end
181+
// except for the first index when the returned label does not end with '.'.
182+
func SetChildrenMappedLabels(manager content.Manager, f HandlerFunc, labelMap func(ocispec.Descriptor) []string) HandlerFunc {
183+
if labelMap == nil {
184+
labelMap = ChildGCLabels
185+
}
173186
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
174187
children, err := f(ctx, desc)
175188
if err != nil {
176189
return children, err
177190
}
178191

179192
if len(children) > 0 {
180-
info := content.Info{
181-
Digest: desc.Digest,
182-
Labels: map[string]string{},
183-
}
184-
fields := []string{}
185-
for i, ch := range children {
186-
info.Labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = ch.Digest.String()
187-
fields = append(fields, fmt.Sprintf("labels.containerd.io/gc.ref.content.%d", i))
193+
var (
194+
info = content.Info{
195+
Digest: desc.Digest,
196+
Labels: map[string]string{},
197+
}
198+
fields = []string{}
199+
keys = map[string]uint{}
200+
)
201+
for _, ch := range children {
202+
labelKeys := labelMap(ch)
203+
for _, key := range labelKeys {
204+
idx := keys[key]
205+
keys[key] = idx + 1
206+
if idx > 0 || key[len(key)-1] == '.' {
207+
key = fmt.Sprintf("%s%d", key, idx)
208+
}
209+
210+
info.Labels[key] = ch.Digest.String()
211+
fields = append(fields, "labels."+key)
212+
}
188213
}
189214

190215
_, err := manager.Update(ctx, info, fields...)

images/mediatypes.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,31 @@ func IsKnownConfig(mt string) bool {
124124
}
125125
return false
126126
}
127+
128+
// ChildGCLabels returns the label for a given descriptor to reference it
129+
func ChildGCLabels(desc ocispec.Descriptor) []string {
130+
mt := desc.MediaType
131+
if IsKnownConfig(mt) {
132+
return []string{"containerd.io/gc.ref.content.config"}
133+
}
134+
135+
switch mt {
136+
case MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
137+
return []string{"containerd.io/gc.ref.content.m."}
138+
}
139+
140+
if IsLayerType(mt) {
141+
return []string{"containerd.io/gc.ref.content.l."}
142+
}
143+
144+
return []string{"containerd.io/gc.ref.content."}
145+
}
146+
147+
// ChildGCLabelsFilterLayers returns the labels for a given descriptor to
148+
// reference it, skipping layer media types
149+
func ChildGCLabelsFilterLayers(desc ocispec.Descriptor) []string {
150+
if IsLayerType(desc.MediaType) {
151+
return nil
152+
}
153+
return ChildGCLabels(desc)
154+
}

pull.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ func (c *Client) fetch(ctx context.Context, rCtx *RemoteContext, ref string, lim
159159
// Get all the children for a descriptor
160160
childrenHandler := images.ChildrenHandler(store)
161161
// Set any children labels for that content
162-
childrenHandler = images.SetChildrenLabels(store, childrenHandler)
162+
childrenHandler = images.SetChildrenMappedLabels(store, childrenHandler, rCtx.ChildLabelMap)
163163
if rCtx.AllMetadata {
164164
// Filter manifests by platforms but allow to handle manifest
165165
// and configuration for not-target platforms

unpacker.go

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"encoding/json"
2323
"fmt"
2424
"math/rand"
25-
"strings"
2625
"sync"
2726
"sync/atomic"
2827
"time"
@@ -78,7 +77,6 @@ func (u *unpacker) unpack(
7877
rCtx *RemoteContext,
7978
h images.Handler,
8079
config ocispec.Descriptor,
81-
parentDesc ocispec.Descriptor,
8280
layers []ocispec.Descriptor,
8381
) error {
8482
p, err := content.ReadBlob(ctx, u.c.ContentStore(), config)
@@ -247,31 +245,6 @@ EachLayer:
247245
"chainID": chainID,
248246
}).Debug("image unpacked")
249247

250-
if rCtx.DiscardContent {
251-
// delete references to successfully unpacked layers
252-
layersMap := map[string]struct{}{}
253-
for _, desc := range layers {
254-
layersMap[desc.Digest.String()] = struct{}{}
255-
}
256-
pinfo, err := cs.Info(ctx, parentDesc.Digest)
257-
if err != nil {
258-
return err
259-
}
260-
fields := []string{}
261-
for k, v := range pinfo.Labels {
262-
if strings.HasPrefix(k, "containerd.io/gc.ref.content.") {
263-
if _, ok := layersMap[v]; ok {
264-
// We've already unpacked this layer content
265-
pinfo.Labels[k] = ""
266-
fields = append(fields, "labels."+k)
267-
}
268-
}
269-
}
270-
if _, err := cs.Update(ctx, pinfo, fields...); err != nil {
271-
return err
272-
}
273-
}
274-
275248
return nil
276249
}
277250

@@ -314,7 +287,6 @@ func (u *unpacker) handlerWrapper(
314287
var (
315288
lock sync.Mutex
316289
layers = map[digest.Digest][]ocispec.Descriptor{}
317-
parent = map[digest.Digest]ocispec.Descriptor{}
318290
)
319291
return images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
320292
children, err := f.Handle(ctx, desc)
@@ -340,20 +312,18 @@ func (u *unpacker) handlerWrapper(
340312
lock.Lock()
341313
for _, nl := range nonLayers {
342314
layers[nl.Digest] = manifestLayers
343-
parent[nl.Digest] = desc
344315
}
345316
lock.Unlock()
346317

347318
children = nonLayers
348319
case images.MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig:
349320
lock.Lock()
350321
l := layers[desc.Digest]
351-
p := parent[desc.Digest]
352322
lock.Unlock()
353323
if len(l) > 0 {
354324
atomic.AddInt32(unpacks, 1)
355325
eg.Go(func() error {
356-
return u.unpack(uctx, rCtx, f, desc, p, l)
326+
return u.unpack(uctx, rCtx, f, desc, l)
357327
})
358328
}
359329
}

0 commit comments

Comments
 (0)