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:
parent
03ab1b2cac
commit
c8b14ae4c0
@ -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 {
|
||||||
|
@ -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.
|
||||||
|
@ -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 {
|
||||||
|
@ -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...)
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
2
pull.go
2
pull.go
@ -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
|
||||||
|
32
unpacker.go
32
unpacker.go
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user