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:
Derek McGowan
2017-07-05 17:00:11 -07:00
parent 1a49f5ea79
commit fba7463ed3
14 changed files with 973 additions and 160 deletions

View File

@@ -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
})
}

View File

@@ -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

View File

@@ -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