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>
This commit is contained in:
Derek McGowan 2020-07-21 00:32:31 -07:00
parent 03ab1b2cac
commit c8b14ae4c0
No known key found for this signature in database
GPG Key ID: F58C5D0A4405ACDB
7 changed files with 86 additions and 54 deletions

View File

@ -312,11 +312,6 @@ type RemoteContext struct {
// afterwards. Unpacking is required to run an image. // afterwards. Unpacking is required to run an image.
Unpack bool Unpack bool
// DiscardContent is a boolean flag to specify whether to allow GC to clean
// layers up from the content store after successfully unpacking these
// contents to the snapshotter.
DiscardContent bool
// UnpackOpts handles options to the unpack call. // UnpackOpts handles options to the unpack call.
UnpackOpts []UnpackOpt UnpackOpts []UnpackOpt
@ -356,6 +351,10 @@ type RemoteContext struct {
// AllMetadata downloads all manifests and known-configuration files // AllMetadata downloads all manifests and known-configuration files
AllMetadata bool AllMetadata bool
// ChildLabelMap sets the labels used to reference child objects in the content
// store. By default, all GC reference labels will be set for all fetched content.
ChildLabelMap func(ocispec.Descriptor) []string
} }
func defaultRemoteContext() *RemoteContext { func defaultRemoteContext() *RemoteContext {

View File

@ -23,6 +23,7 @@ import (
"github.com/containerd/containerd/platforms" "github.com/containerd/containerd/platforms"
"github.com/containerd/containerd/remotes" "github.com/containerd/containerd/remotes"
"github.com/containerd/containerd/snapshots" "github.com/containerd/containerd/snapshots"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
@ -132,14 +133,6 @@ func WithPullUnpack(_ *Client, c *RemoteContext) error {
return nil return nil
} }
// WithDiscardContent is used to allow GC to clean layers up from
// the content store after successfully unpacking these contents to
// the snapshotter.
func WithDiscardContent(_ *Client, c *RemoteContext) error {
c.DiscardContent = true
return nil
}
// WithUnpackOpts is used to add unpack options to the unpacker. // WithUnpackOpts is used to add unpack options to the unpacker.
func WithUnpackOpts(opts []UnpackOpt) RemoteOpt { func WithUnpackOpts(opts []UnpackOpt) RemoteOpt {
return func(_ *Client, c *RemoteContext) error { return func(_ *Client, c *RemoteContext) error {
@ -183,6 +176,18 @@ func WithPullLabels(labels map[string]string) RemoteOpt {
} }
} }
// WithChildLabelMap sets the map function used to define the labels set
// on referenced child content in the content store. This can be used
// to overwrite the default GC labels or filter which labels get set
// for content.
// The default is `images.ChildGCLabels`.
func WithChildLabelMap(fn func(ocispec.Descriptor) []string) RemoteOpt {
return func(_ *Client, c *RemoteContext) error {
c.ChildLabelMap = fn
return nil
}
}
// WithSchema1Conversion is used to convert Docker registry schema 1 // WithSchema1Conversion is used to convert Docker registry schema 1
// manifests to oci manifests on pull. Without this option schema 1 // manifests to oci manifests on pull. Without this option schema 1
// manifests will return a not supported error. // manifests will return a not supported error.

View File

@ -229,6 +229,11 @@ func TestImagePullWithDiscardContent(t *testing.T) {
ctx, cancel := testContext(t) ctx, cancel := testContext(t)
defer cancel() defer cancel()
err = client.ImageService().Delete(ctx, testImage, images.SynchronousDelete())
if err != nil {
t.Fatal(err)
}
ls := client.LeasesService() ls := client.LeasesService()
l, err := ls.Create(ctx, leases.WithRandomID(), leases.WithExpiration(24*time.Hour)) l, err := ls.Create(ctx, leases.WithRandomID(), leases.WithExpiration(24*time.Hour))
if err != nil { if err != nil {
@ -238,7 +243,7 @@ func TestImagePullWithDiscardContent(t *testing.T) {
img, err := client.Pull(ctx, testImage, img, err := client.Pull(ctx, testImage,
WithPlatformMatcher(platforms.Default()), WithPlatformMatcher(platforms.Default()),
WithPullUnpack, WithPullUnpack,
WithDiscardContent, WithChildLabelMap(images.ChildGCLabelsFilterLayers),
) )
// Synchronously garbage collect contents // Synchronously garbage collect contents
if errL := ls.Delete(ctx, l, leases.SynchronousDelete); errL != nil { if errL := ls.Delete(ctx, l, leases.SynchronousDelete); errL != nil {

View File

@ -170,6 +170,19 @@ func ChildrenHandler(provider content.Provider) HandlerFunc {
// the children returned by the handler and passes through the children. // the children returned by the handler and passes through the children.
// Must follow a handler that returns the children to be labeled. // Must follow a handler that returns the children to be labeled.
func SetChildrenLabels(manager content.Manager, f HandlerFunc) HandlerFunc { func SetChildrenLabels(manager content.Manager, f HandlerFunc) HandlerFunc {
return SetChildrenMappedLabels(manager, f, nil)
}
// SetChildrenMappedLabels is a handler wrapper which sets labels for the content on
// the children returned by the handler and passes through the children.
// Must follow a handler that returns the children to be labeled.
// The label map allows the caller to control the labels per child descriptor.
// For returned labels, the index of the child will be appended to the end
// except for the first index when the returned label does not end with '.'.
func SetChildrenMappedLabels(manager content.Manager, f HandlerFunc, labelMap func(ocispec.Descriptor) []string) HandlerFunc {
if labelMap == nil {
labelMap = ChildGCLabels
}
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
children, err := f(ctx, desc) children, err := f(ctx, desc)
if err != nil { if err != nil {
@ -177,14 +190,26 @@ func SetChildrenLabels(manager content.Manager, f HandlerFunc) HandlerFunc {
} }
if len(children) > 0 { if len(children) > 0 {
info := content.Info{ var (
Digest: desc.Digest, info = content.Info{
Labels: map[string]string{}, Digest: desc.Digest,
} Labels: map[string]string{},
fields := []string{} }
for i, ch := range children { fields = []string{}
info.Labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = ch.Digest.String() keys = map[string]uint{}
fields = append(fields, fmt.Sprintf("labels.containerd.io/gc.ref.content.%d", i)) )
for _, ch := range children {
labelKeys := labelMap(ch)
for _, key := range labelKeys {
idx := keys[key]
keys[key] = idx + 1
if idx > 0 || key[len(key)-1] == '.' {
key = fmt.Sprintf("%s%d", key, idx)
}
info.Labels[key] = ch.Digest.String()
fields = append(fields, "labels."+key)
}
} }
_, err := manager.Update(ctx, info, fields...) _, err := manager.Update(ctx, info, fields...)

View File

@ -124,3 +124,31 @@ func IsKnownConfig(mt string) bool {
} }
return false return false
} }
// ChildGCLabels returns the label for a given descriptor to reference it
func ChildGCLabels(desc ocispec.Descriptor) []string {
mt := desc.MediaType
if IsKnownConfig(mt) {
return []string{"containerd.io/gc.ref.content.config"}
}
switch mt {
case MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
return []string{"containerd.io/gc.ref.content.m."}
}
if IsLayerType(mt) {
return []string{"containerd.io/gc.ref.content.l."}
}
return []string{"containerd.io/gc.ref.content."}
}
// ChildGCLabelsFilterLayers returns the labels for a given descriptor to
// reference it, skipping layer media types
func ChildGCLabelsFilterLayers(desc ocispec.Descriptor) []string {
if IsLayerType(desc.MediaType) {
return nil
}
return ChildGCLabels(desc)
}

View File

@ -159,7 +159,7 @@ func (c *Client) fetch(ctx context.Context, rCtx *RemoteContext, ref string, lim
// Get all the children for a descriptor // Get all the children for a descriptor
childrenHandler := images.ChildrenHandler(store) childrenHandler := images.ChildrenHandler(store)
// Set any children labels for that content // Set any children labels for that content
childrenHandler = images.SetChildrenLabels(store, childrenHandler) childrenHandler = images.SetChildrenMappedLabels(store, childrenHandler, rCtx.ChildLabelMap)
if rCtx.AllMetadata { if rCtx.AllMetadata {
// Filter manifests by platforms but allow to handle manifest // Filter manifests by platforms but allow to handle manifest
// and configuration for not-target platforms // and configuration for not-target platforms

View File

@ -22,7 +22,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"math/rand" "math/rand"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -78,7 +77,6 @@ func (u *unpacker) unpack(
rCtx *RemoteContext, rCtx *RemoteContext,
h images.Handler, h images.Handler,
config ocispec.Descriptor, config ocispec.Descriptor,
parentDesc ocispec.Descriptor,
layers []ocispec.Descriptor, layers []ocispec.Descriptor,
) error { ) error {
p, err := content.ReadBlob(ctx, u.c.ContentStore(), config) p, err := content.ReadBlob(ctx, u.c.ContentStore(), config)
@ -247,31 +245,6 @@ EachLayer:
"chainID": chainID, "chainID": chainID,
}).Debug("image unpacked") }).Debug("image unpacked")
if rCtx.DiscardContent {
// delete references to successfully unpacked layers
layersMap := map[string]struct{}{}
for _, desc := range layers {
layersMap[desc.Digest.String()] = struct{}{}
}
pinfo, err := cs.Info(ctx, parentDesc.Digest)
if err != nil {
return err
}
fields := []string{}
for k, v := range pinfo.Labels {
if strings.HasPrefix(k, "containerd.io/gc.ref.content.") {
if _, ok := layersMap[v]; ok {
// We've already unpacked this layer content
pinfo.Labels[k] = ""
fields = append(fields, "labels."+k)
}
}
}
if _, err := cs.Update(ctx, pinfo, fields...); err != nil {
return err
}
}
return nil return nil
} }
@ -314,7 +287,6 @@ func (u *unpacker) handlerWrapper(
var ( var (
lock sync.Mutex lock sync.Mutex
layers = map[digest.Digest][]ocispec.Descriptor{} layers = map[digest.Digest][]ocispec.Descriptor{}
parent = map[digest.Digest]ocispec.Descriptor{}
) )
return images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { return images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
children, err := f.Handle(ctx, desc) children, err := f.Handle(ctx, desc)
@ -340,7 +312,6 @@ func (u *unpacker) handlerWrapper(
lock.Lock() lock.Lock()
for _, nl := range nonLayers { for _, nl := range nonLayers {
layers[nl.Digest] = manifestLayers layers[nl.Digest] = manifestLayers
parent[nl.Digest] = desc
} }
lock.Unlock() lock.Unlock()
@ -348,12 +319,11 @@ func (u *unpacker) handlerWrapper(
case images.MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig: case images.MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig:
lock.Lock() lock.Lock()
l := layers[desc.Digest] l := layers[desc.Digest]
p := parent[desc.Digest]
lock.Unlock() lock.Unlock()
if len(l) > 0 { if len(l) > 0 {
atomic.AddInt32(unpacks, 1) atomic.AddInt32(unpacks, 1)
eg.Go(func() error { eg.Go(func() error {
return u.unpack(uctx, rCtx, f, desc, p, l) return u.unpack(uctx, rCtx, f, desc, l)
}) })
} }
} }