Support for ingest namespacing
Move content status to list statuses and add single status to interface. Updates API to support list statuses and status Updates snapshot key creation to be generic Signed-off-by: Derek McGowan <derek@mcgstyle.net>
This commit is contained in:
@@ -4,11 +4,14 @@ import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"regexp"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type contentStore struct {
|
||||
@@ -33,9 +36,9 @@ func (cs *contentStore) Info(ctx context.Context, dgst digest.Digest) (content.I
|
||||
|
||||
var info content.Info
|
||||
if err := view(ctx, cs.db, func(tx *bolt.Tx) error {
|
||||
bkt := getContentBucket(tx, ns, dgst)
|
||||
bkt := getBlobBucket(tx, ns, dgst)
|
||||
if bkt == nil {
|
||||
return content.ErrNotFound("")
|
||||
return errors.Wrapf(errdefs.ErrNotFound, "content digest %v", dgst)
|
||||
}
|
||||
|
||||
info.Digest = dgst
|
||||
@@ -56,7 +59,7 @@ func (cs *contentStore) Walk(ctx context.Context, fn content.WalkFunc) error {
|
||||
// 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 {
|
||||
bkt := getAllContentBucket(tx, ns)
|
||||
bkt := getBlobsBucket(tx, ns)
|
||||
if bkt == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -95,37 +98,121 @@ func (cs *contentStore) Delete(ctx context.Context, dgst digest.Digest) error {
|
||||
}
|
||||
|
||||
return update(ctx, cs.db, func(tx *bolt.Tx) error {
|
||||
bkt := getContentBucket(tx, ns, dgst)
|
||||
bkt := getBlobBucket(tx, ns, dgst)
|
||||
if bkt == nil {
|
||||
return content.ErrNotFound("")
|
||||
return errors.Wrapf(errdefs.ErrNotFound, "content digest %v", dgst)
|
||||
}
|
||||
|
||||
// Just remove local reference, garbage collector is responsible for
|
||||
// cleaning up on disk content
|
||||
return getAllContentBucket(tx, ns).Delete([]byte(dgst.String()))
|
||||
return getBlobsBucket(tx, ns).Delete([]byte(dgst.String()))
|
||||
})
|
||||
}
|
||||
|
||||
func (cs *contentStore) Status(ctx context.Context, re string) ([]content.Status, error) {
|
||||
_, err := namespaces.NamespaceRequired(ctx)
|
||||
func (cs *contentStore) ListStatuses(ctx context.Context, re string) ([]content.Status, error) {
|
||||
ns, err := namespaces.NamespaceRequired(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: Read status keys and match
|
||||
var rec *regexp.Regexp
|
||||
if re != "" {
|
||||
rec, err = regexp.Compile(re)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return cs.Store.Status(ctx, re)
|
||||
var brefs []string
|
||||
if err := view(ctx, cs.db, func(tx *bolt.Tx) error {
|
||||
bkt := getIngestBucket(tx, ns)
|
||||
if bkt == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return bkt.ForEach(func(k, v []byte) error {
|
||||
if rec == nil || rec.Match(k) {
|
||||
brefs = append(brefs, string(v))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
statuses := make([]content.Status, 0, len(brefs))
|
||||
for _, bref := range brefs {
|
||||
status, err := cs.Store.Status(ctx, bref)
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
status.Ref = trimKey(status.Ref)
|
||||
|
||||
statuses = append(statuses, status)
|
||||
}
|
||||
|
||||
return statuses, nil
|
||||
|
||||
}
|
||||
|
||||
func getRef(tx *bolt.Tx, ns, ref string) string {
|
||||
bkt := getIngestBucket(tx, ns)
|
||||
if bkt == nil {
|
||||
return ""
|
||||
}
|
||||
v := bkt.Get([]byte(ref))
|
||||
if len(v) == 0 {
|
||||
return ""
|
||||
}
|
||||
return string(v)
|
||||
}
|
||||
|
||||
func (cs *contentStore) Status(ctx context.Context, ref string) (content.Status, error) {
|
||||
ns, err := namespaces.NamespaceRequired(ctx)
|
||||
if err != nil {
|
||||
return content.Status{}, err
|
||||
}
|
||||
|
||||
var bref string
|
||||
if err := view(ctx, cs.db, func(tx *bolt.Tx) error {
|
||||
bref = getRef(tx, ns, ref)
|
||||
if bref == "" {
|
||||
return errors.Wrapf(errdefs.ErrNotFound, "reference %v", ref)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return content.Status{}, err
|
||||
}
|
||||
|
||||
return cs.Store.Status(ctx, bref)
|
||||
}
|
||||
|
||||
func (cs *contentStore) Abort(ctx context.Context, ref string) error {
|
||||
_, err := namespaces.NamespaceRequired(ctx)
|
||||
ns, err := namespaces.NamespaceRequired(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Read status key and delete
|
||||
return update(ctx, cs.db, func(tx *bolt.Tx) error {
|
||||
bkt := getIngestBucket(tx, ns)
|
||||
if bkt == nil {
|
||||
return errors.Wrapf(errdefs.ErrNotFound, "reference %v", ref)
|
||||
}
|
||||
bref := string(bkt.Get([]byte(ref)))
|
||||
if bref == "" {
|
||||
return errors.Wrapf(errdefs.ErrNotFound, "reference %v", ref)
|
||||
}
|
||||
if err := bkt.Delete([]byte(ref)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cs.Store.Abort(ctx, bref)
|
||||
})
|
||||
|
||||
return cs.Store.Abort(ctx, ref)
|
||||
}
|
||||
|
||||
func (cs *contentStore) Writer(ctx context.Context, ref string, size int64, expected digest.Digest) (content.Writer, error) {
|
||||
@@ -134,29 +221,44 @@ func (cs *contentStore) Writer(ctx context.Context, ref string, size int64, expe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: Create ref key
|
||||
|
||||
if expected != "" {
|
||||
if err := view(ctx, cs.db, func(tx *bolt.Tx) error {
|
||||
bkt := getContentBucket(tx, ns, expected)
|
||||
if bkt != nil {
|
||||
return content.ErrExists("")
|
||||
var w content.Writer
|
||||
if err := update(ctx, cs.db, func(tx *bolt.Tx) error {
|
||||
if expected != "" {
|
||||
cbkt := getBlobBucket(tx, ns, expected)
|
||||
if cbkt != nil {
|
||||
return errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", expected)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Do not use the passed in expected value here since it was
|
||||
// already checked against the user metadata. If the content
|
||||
// store has the content, it must still be written before
|
||||
// linked into the given namespace. It is possible in the future
|
||||
// to allow content which exists in content store but not
|
||||
// namespace to be linked here and returned an exist error, but
|
||||
// this would require more configuration to make secure.
|
||||
w, err := cs.Store.Writer(ctx, ref, size, "")
|
||||
if err != nil {
|
||||
bkt, err := createIngestBucket(tx, ns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(bkt.Get([]byte(ref))) > 0 {
|
||||
return errors.Wrapf(errdefs.ErrUnavailable, "ref %v is currently in use", ref)
|
||||
}
|
||||
|
||||
sid, err := bkt.NextSequence()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bref := createKey(sid, ns, ref)
|
||||
if err := bkt.Put([]byte(ref), []byte(bref)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Do not use the passed in expected value here since it was
|
||||
// already checked against the user metadata. If the content
|
||||
// store has the content, it must still be written before
|
||||
// linked into the given namespace. It is possible in the future
|
||||
// to allow content which exists in content store but not
|
||||
// namespace to be linked here and returned an exist error, but
|
||||
// this would require more configuration to make secure.
|
||||
w, err = cs.Store.Writer(ctx, bref, size, "")
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -164,6 +266,7 @@ func (cs *contentStore) Writer(ctx context.Context, ref string, size int64, expe
|
||||
// when no expected is provided there.
|
||||
return &namespacedWriter{
|
||||
Writer: w,
|
||||
ref: ref,
|
||||
namespace: ns,
|
||||
db: cs.db,
|
||||
}, nil
|
||||
@@ -171,22 +274,21 @@ func (cs *contentStore) Writer(ctx context.Context, ref string, size int64, expe
|
||||
|
||||
type namespacedWriter struct {
|
||||
content.Writer
|
||||
ref string
|
||||
namespace string
|
||||
db *bolt.DB
|
||||
}
|
||||
|
||||
func (nw *namespacedWriter) Commit(size int64, expected digest.Digest) error {
|
||||
tx, err := nw.db.Begin(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := nw.commit(tx, size, expected); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
return nw.db.Update(func(tx *bolt.Tx) error {
|
||||
bkt := getIngestBucket(tx, nw.namespace)
|
||||
if bkt != nil {
|
||||
if err := bkt.Delete([]byte(nw.ref)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nw.commit(tx, size, expected)
|
||||
})
|
||||
}
|
||||
|
||||
func (nw *namespacedWriter) commit(tx *bolt.Tx, size int64, expected digest.Digest) error {
|
||||
@@ -196,20 +298,18 @@ func (nw *namespacedWriter) commit(tx *bolt.Tx, size int64, expected digest.Dige
|
||||
}
|
||||
actual := nw.Writer.Digest()
|
||||
|
||||
// TODO: Handle already exists
|
||||
if err := nw.Writer.Commit(size, expected); err != nil {
|
||||
if !content.IsExists(err) {
|
||||
if !errdefs.IsAlreadyExists(err) {
|
||||
return err
|
||||
}
|
||||
if getContentBucket(tx, nw.namespace, actual) != nil {
|
||||
return content.ErrExists("")
|
||||
if getBlobBucket(tx, nw.namespace, actual) != nil {
|
||||
return errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual)
|
||||
}
|
||||
// Link into this namespace
|
||||
}
|
||||
|
||||
size = status.Total
|
||||
|
||||
bkt, err := createContentBucket(tx, nw.namespace, actual)
|
||||
bkt, err := createBlobBucket(tx, nw.namespace, actual)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -257,9 +357,9 @@ func (cs *contentStore) checkAccess(ctx context.Context, dgst digest.Digest) err
|
||||
}
|
||||
|
||||
return view(ctx, cs.db, func(tx *bolt.Tx) error {
|
||||
bkt := getContentBucket(tx, ns, dgst)
|
||||
bkt := getBlobBucket(tx, ns, dgst)
|
||||
if bkt == nil {
|
||||
return content.ErrNotFound("")
|
||||
return errors.Wrapf(errdefs.ErrNotFound, "content digest %v", dgst)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user