Add labels and fileters to content
Update list content command to support filters Add label subcommand to content in dist tool to update labels Add uncompressed label on unpack Signed-off-by: Derek McGowan <derek@mcgstyle.net>
This commit is contained in:
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/containerd/containerd/content"
|
||||
@@ -50,12 +52,77 @@ func (cs *contentStore) Info(ctx context.Context, dgst digest.Digest) (content.I
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (cs *contentStore) Walk(ctx context.Context, fn content.WalkFunc) error {
|
||||
func (cs *contentStore) Update(ctx context.Context, info content.Info, fieldpaths ...string) error {
|
||||
ns, err := namespaces.NamespaceRequired(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := update(ctx, cs.db, func(tx *bolt.Tx) error {
|
||||
bkt := getBlobBucket(tx, ns, info.Digest)
|
||||
if bkt == nil {
|
||||
return errors.Wrapf(errdefs.ErrNotFound, "content digest %v", info.Digest)
|
||||
}
|
||||
|
||||
updated := content.Info{
|
||||
Digest: info.Digest,
|
||||
}
|
||||
|
||||
if err := readInfo(&updated, bkt); err != nil {
|
||||
return errors.Wrapf(err, "info %q", info.Digest)
|
||||
}
|
||||
|
||||
if len(fieldpaths) > 0 {
|
||||
for _, path := range fieldpaths {
|
||||
if strings.HasPrefix(path, "labels.") {
|
||||
if updated.Labels == nil {
|
||||
updated.Labels = map[string]string{}
|
||||
}
|
||||
|
||||
key := strings.TrimPrefix(path, "labels.")
|
||||
updated.Labels[key] = info.Labels[key]
|
||||
continue
|
||||
}
|
||||
|
||||
switch path {
|
||||
case "labels":
|
||||
updated.Labels = info.Labels
|
||||
default:
|
||||
return errors.Wrapf(errdefs.ErrInvalidArgument, "cannot update %q field on content info %q", path, info.Digest)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info.CommittedAt = updated.CommittedAt
|
||||
updated = info
|
||||
}
|
||||
|
||||
updated.UpdatedAt = time.Now().UTC()
|
||||
return writeInfo(&updated, bkt)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *contentStore) Walk(ctx context.Context, fn content.WalkFunc, fs ...string) error {
|
||||
ns, err := namespaces.NamespaceRequired(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var filter filters.Filter = filters.Always
|
||||
if len(fs) > 0 {
|
||||
fa := make([]filters.Filter, 0, len(fs))
|
||||
for _, s := range fs {
|
||||
f, err := filters.Parse(s)
|
||||
if err != nil {
|
||||
return errors.Wrapf(errdefs.ErrInvalidArgument, "bad filter %q", s)
|
||||
}
|
||||
fa = append(fa, f)
|
||||
}
|
||||
filter = filters.Filter(filters.Any(fa))
|
||||
}
|
||||
|
||||
// TODO: Batch results to keep from reading all info into memory
|
||||
var infos []content.Info
|
||||
if err := view(ctx, cs.db, func(tx *bolt.Tx) error {
|
||||
@@ -67,6 +134,11 @@ func (cs *contentStore) Walk(ctx context.Context, fn content.WalkFunc) error {
|
||||
return bkt.ForEach(func(k, v []byte) error {
|
||||
dgst, err := digest.Parse(string(k))
|
||||
if err != nil {
|
||||
// Not a digest, skip
|
||||
return nil
|
||||
}
|
||||
bbkt := bkt.Bucket(k)
|
||||
if bbkt == nil {
|
||||
return nil
|
||||
}
|
||||
info := content.Info{
|
||||
@@ -75,7 +147,9 @@ func (cs *contentStore) Walk(ctx context.Context, fn content.WalkFunc) error {
|
||||
if err := readInfo(&info, bkt.Bucket(k)); err != nil {
|
||||
return err
|
||||
}
|
||||
infos = append(infos, info)
|
||||
if filter.Match(adaptContentInfo(info)) {
|
||||
infos = append(infos, info)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
@@ -367,16 +441,62 @@ func (cs *contentStore) checkAccess(ctx context.Context, dgst digest.Digest) err
|
||||
}
|
||||
|
||||
func readInfo(info *content.Info, bkt *bolt.Bucket) error {
|
||||
return bkt.ForEach(func(k, v []byte) error {
|
||||
switch string(k) {
|
||||
case string(bucketKeyCreatedAt):
|
||||
if err := info.CommittedAt.UnmarshalBinary(v); err != nil {
|
||||
return err
|
||||
}
|
||||
case string(bucketKeySize):
|
||||
info.Size, _ = binary.Varint(v)
|
||||
if err := readTimestamps(&info.CommittedAt, &info.UpdatedAt, bkt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lbkt := bkt.Bucket(bucketKeyLabels)
|
||||
if lbkt != nil {
|
||||
info.Labels = map[string]string{}
|
||||
if err := readLabels(info.Labels, lbkt); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: Read labels
|
||||
return nil
|
||||
}
|
||||
|
||||
if v := bkt.Get(bucketKeySize); len(v) > 0 {
|
||||
info.Size, _ = binary.Varint(v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeInfo(info *content.Info, bkt *bolt.Bucket) error {
|
||||
if err := writeTimestamps(bkt, info.CommittedAt, info.UpdatedAt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeLabels(bkt, info.Labels); err != nil {
|
||||
return errors.Wrapf(err, "writing labels for info %v", info.Digest)
|
||||
}
|
||||
|
||||
// Write size
|
||||
sizeEncoded, err := encodeSize(info.Size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := bkt.Put(bucketKeySize, sizeEncoded); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func adaptContentInfo(info content.Info) filters.Adaptor {
|
||||
return filters.AdapterFunc(func(fieldpath []string) (string, bool) {
|
||||
if len(fieldpath) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
switch fieldpath[0] {
|
||||
case "digest":
|
||||
return info.Digest.String(), true
|
||||
case "size":
|
||||
// TODO: support size based filtering
|
||||
case "labels":
|
||||
return checkMap(fieldpath[1:], info.Labels)
|
||||
}
|
||||
|
||||
return "", false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@ func writeLabels(bkt *bolt.Bucket, labels map[string]string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if len(labels) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
lbkt, err := bkt.CreateBucket(bucketKeyLabels)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -232,10 +232,8 @@ func writeImage(bkt *bolt.Bucket, image *images.Image) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(image.Labels) > 0 {
|
||||
if err := writeLabels(bkt, image.Labels); err != nil {
|
||||
return errors.Wrapf(err, "writing labels for image %v", image.Name)
|
||||
}
|
||||
if err := writeLabels(bkt, image.Labels); err != nil {
|
||||
return errors.Wrapf(err, "writing labels for image %v", image.Name)
|
||||
}
|
||||
|
||||
// write the target bucket
|
||||
|
||||
Reference in New Issue
Block a user