containerd/metadata/sandbox.go
Derek McGowan 5fdf55e493
Update go module to github.com/containerd/containerd/v2
Signed-off-by: Derek McGowan <derek@mcg.dev>
2023-10-29 20:52:21 -07:00

385 lines
9.1 KiB
Go

/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package metadata
import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/containerd/containerd/v2/errdefs"
"github.com/containerd/containerd/v2/filters"
"github.com/containerd/containerd/v2/identifiers"
"github.com/containerd/containerd/v2/metadata/boltutil"
"github.com/containerd/containerd/v2/namespaces"
api "github.com/containerd/containerd/v2/sandbox"
"github.com/containerd/typeurl/v2"
"go.etcd.io/bbolt"
)
type sandboxStore struct {
db *DB
}
var _ api.Store = (*sandboxStore)(nil)
// NewSandboxStore creates a datababase client for sandboxes
func NewSandboxStore(db *DB) api.Store {
return &sandboxStore{db: db}
}
// Create a sandbox record in the store
func (s *sandboxStore) Create(ctx context.Context, sandbox api.Sandbox) (api.Sandbox, error) {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return api.Sandbox{}, err
}
sandbox.CreatedAt = time.Now().UTC()
sandbox.UpdatedAt = sandbox.CreatedAt
if err := s.validate(&sandbox); err != nil {
return api.Sandbox{}, fmt.Errorf("failed to validate sandbox: %w", err)
}
if err := update(ctx, s.db, func(tx *bbolt.Tx) error {
parent, err := createSandboxBucket(tx, ns)
if err != nil {
return fmt.Errorf("create error: %w", err)
}
if err := s.write(parent, &sandbox, false); err != nil {
return fmt.Errorf("write error: %w", err)
}
return nil
}); err != nil {
return api.Sandbox{}, err
}
return sandbox, nil
}
// Update the sandbox with the provided sandbox object and fields
func (s *sandboxStore) Update(ctx context.Context, sandbox api.Sandbox, fieldpaths ...string) (api.Sandbox, error) {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return api.Sandbox{}, err
}
ret := api.Sandbox{}
if err := update(ctx, s.db, func(tx *bbolt.Tx) error {
parent := getSandboxBucket(tx, ns)
if parent == nil {
return fmt.Errorf("no sandbox buckets: %w", errdefs.ErrNotFound)
}
updated, err := s.read(parent, []byte(sandbox.ID))
if err != nil {
return err
}
if len(fieldpaths) == 0 {
fieldpaths = []string{"labels", "extensions", "spec", "runtime"}
if updated.Runtime.Name != sandbox.Runtime.Name {
return fmt.Errorf("sandbox.Runtime.Name field is immutable: %w", errdefs.ErrInvalidArgument)
}
}
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] = sandbox.Labels[key]
continue
} else if strings.HasPrefix(path, "extensions.") {
if updated.Extensions == nil {
updated.Extensions = map[string]typeurl.Any{}
}
key := strings.TrimPrefix(path, "extensions.")
updated.Extensions[key] = sandbox.Extensions[key]
continue
}
switch path {
case "labels":
updated.Labels = sandbox.Labels
case "extensions":
updated.Extensions = sandbox.Extensions
case "runtime":
updated.Runtime = sandbox.Runtime
case "spec":
updated.Spec = sandbox.Spec
default:
return fmt.Errorf("cannot update %q field on sandbox %q: %w", path, sandbox.ID, errdefs.ErrInvalidArgument)
}
}
updated.UpdatedAt = time.Now().UTC()
if err := s.write(parent, &updated, true); err != nil {
return err
}
ret = updated
return nil
}); err != nil {
return api.Sandbox{}, err
}
return ret, nil
}
// Get sandbox metadata using the id
func (s *sandboxStore) Get(ctx context.Context, id string) (api.Sandbox, error) {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return api.Sandbox{}, err
}
ret := api.Sandbox{}
if err := view(ctx, s.db, func(tx *bbolt.Tx) error {
bucket := getSandboxBucket(tx, ns)
if bucket == nil {
return fmt.Errorf("no sandbox buckets: %w", errdefs.ErrNotFound)
}
out, err := s.read(bucket, []byte(id))
if err != nil {
return err
}
ret = out
return nil
}); err != nil {
return api.Sandbox{}, err
}
return ret, nil
}
// List returns sandboxes that match one or more of the provided filters
func (s *sandboxStore) List(ctx context.Context, fields ...string) ([]api.Sandbox, error) {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return nil, err
}
filter, err := filters.ParseAll(fields...)
if err != nil {
return nil, fmt.Errorf("%s: %w", err.Error(), errdefs.ErrInvalidArgument)
}
var (
list []api.Sandbox
)
if err := view(ctx, s.db, func(tx *bbolt.Tx) error {
bucket := getSandboxBucket(tx, ns)
if bucket == nil {
// We haven't created any sandboxes yet, just return empty list
return nil
}
if err := bucket.ForEach(func(k, v []byte) error {
info, err := s.read(bucket, k)
if err != nil {
return fmt.Errorf("failed to read bucket %q: %w", string(k), err)
}
if filter.Match(adaptSandbox(&info)) {
list = append(list, info)
}
return nil
}); err != nil {
return err
}
return nil
}); err != nil {
return nil, err
}
return list, nil
}
// Delete a sandbox from metadata store using the id
func (s *sandboxStore) Delete(ctx context.Context, id string) error {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return err
}
if err := update(ctx, s.db, func(tx *bbolt.Tx) error {
buckets := getSandboxBucket(tx, ns)
if buckets == nil {
return fmt.Errorf("no sandbox buckets: %w", errdefs.ErrNotFound)
}
if err := buckets.DeleteBucket([]byte(id)); err != nil {
if err == bbolt.ErrBucketNotFound {
err = errdefs.ErrNotFound
}
return fmt.Errorf("failed to delete sandbox %q: %w", id, err)
}
return nil
}); err != nil {
return err
}
return nil
}
func (s *sandboxStore) write(parent *bbolt.Bucket, instance *api.Sandbox, overwrite bool) error {
if err := s.validate(instance); err != nil {
return err
}
var (
bucket *bbolt.Bucket
err error
id = []byte(instance.ID)
)
if overwrite {
bucket, err = parent.CreateBucketIfNotExists(id)
if err != nil {
return err
}
} else {
bucket, err = parent.CreateBucket(id)
if err != nil {
if err == bbolt.ErrBucketExists {
return fmt.Errorf("sandbox bucket %q already exists: %w", instance.ID, errdefs.ErrAlreadyExists)
}
return err
}
}
if err := boltutil.WriteTimestamps(bucket, instance.CreatedAt, instance.UpdatedAt); err != nil {
return err
}
if err := boltutil.WriteLabels(bucket, instance.Labels); err != nil {
return err
}
if err := boltutil.WriteExtensions(bucket, instance.Extensions); err != nil {
return err
}
if err := boltutil.WriteAny(bucket, bucketKeySpec, instance.Spec); err != nil {
return err
}
if err := bucket.Put(bucketKeySandboxer, []byte(instance.Sandboxer)); err != nil {
return err
}
runtimeBucket, err := bucket.CreateBucketIfNotExists(bucketKeyRuntime)
if err != nil {
return err
}
if err := runtimeBucket.Put(bucketKeyName, []byte(instance.Runtime.Name)); err != nil {
return err
}
if err := boltutil.WriteAny(runtimeBucket, bucketKeyOptions, instance.Runtime.Options); err != nil {
return err
}
return nil
}
func (s *sandboxStore) read(parent *bbolt.Bucket, id []byte) (api.Sandbox, error) {
var (
inst api.Sandbox
err error
)
bucket := parent.Bucket(id)
if bucket == nil {
return api.Sandbox{}, fmt.Errorf("bucket %q not found: %w", id, errdefs.ErrNotFound)
}
inst.ID = string(id)
inst.Labels, err = boltutil.ReadLabels(bucket)
if err != nil {
return api.Sandbox{}, err
}
if err := boltutil.ReadTimestamps(bucket, &inst.CreatedAt, &inst.UpdatedAt); err != nil {
return api.Sandbox{}, err
}
inst.Spec, err = boltutil.ReadAny(bucket, bucketKeySpec)
if err != nil {
return api.Sandbox{}, err
}
runtimeBucket := bucket.Bucket(bucketKeyRuntime)
if runtimeBucket == nil {
return api.Sandbox{}, errors.New("no runtime bucket")
}
inst.Runtime.Name = string(runtimeBucket.Get(bucketKeyName))
inst.Runtime.Options, err = boltutil.ReadAny(runtimeBucket, bucketKeyOptions)
if err != nil {
return api.Sandbox{}, err
}
inst.Extensions, err = boltutil.ReadExtensions(bucket)
if err != nil {
return api.Sandbox{}, err
}
sandboxer := bucket.Get(bucketKeySandboxer)
if sandboxer == nil {
inst.Sandboxer = ""
} else {
inst.Sandboxer = string(sandboxer)
}
return inst, nil
}
func (s *sandboxStore) validate(new *api.Sandbox) error {
if err := identifiers.Validate(new.ID); err != nil {
return fmt.Errorf("invalid sandbox ID: %w", err)
}
if new.CreatedAt.IsZero() {
return fmt.Errorf("creation date must not be zero: %w", errdefs.ErrInvalidArgument)
}
if new.UpdatedAt.IsZero() {
return fmt.Errorf("updated date must not be zero: %w", errdefs.ErrInvalidArgument)
}
return nil
}