ea41722902
Small refactoring of storagedriver errors. We change the Enclosed field to Detail and make sure Errors get properly serialized to JSON. We also add tests. Signed-off-by: Milos Gajdos <milosthegajdos@gmail.com>
236 lines
8.2 KiB
Go
236 lines
8.2 KiB
Go
// Package base provides a base implementation of the storage driver that can
|
|
// be used to implement common checks. The goal is to increase the amount of
|
|
// code sharing.
|
|
//
|
|
// The canonical approach to use this class is to embed in the exported driver
|
|
// struct such that calls are proxied through this implementation. First,
|
|
// declare the internal driver, as follows:
|
|
//
|
|
// type driver struct { ... internal ...}
|
|
//
|
|
// The resulting type should implement StorageDriver such that it can be the
|
|
// target of a Base struct. The exported type can then be declared as follows:
|
|
//
|
|
// type Driver struct {
|
|
// Base
|
|
// }
|
|
//
|
|
// Because Driver embeds Base, it effectively implements Base. If the driver
|
|
// needs to intercept a call, before going to base, Driver should implement
|
|
// that method. Effectively, Driver can intercept calls before coming in and
|
|
// driver implements the actual logic.
|
|
//
|
|
// To further shield the embed from other packages, it is recommended to
|
|
// employ a private embed struct:
|
|
//
|
|
// type baseEmbed struct {
|
|
// base.Base
|
|
// }
|
|
//
|
|
// Then, declare driver to embed baseEmbed, rather than Base directly:
|
|
//
|
|
// type Driver struct {
|
|
// baseEmbed
|
|
// }
|
|
//
|
|
// The type now implements StorageDriver, proxying through Base, without
|
|
// exporting an unnecessary field.
|
|
package base
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"time"
|
|
|
|
dcontext "github.com/distribution/distribution/v3/context"
|
|
prometheus "github.com/distribution/distribution/v3/metrics"
|
|
storagedriver "github.com/distribution/distribution/v3/registry/storage/driver"
|
|
"github.com/docker/go-metrics"
|
|
)
|
|
|
|
// storageAction is the metrics of blob related operations
|
|
var storageAction = prometheus.StorageNamespace.NewLabeledTimer("action", "The number of seconds that the storage action takes", "driver", "action")
|
|
|
|
func init() {
|
|
metrics.Register(prometheus.StorageNamespace)
|
|
}
|
|
|
|
// Base provides a wrapper around a storagedriver implementation that provides
|
|
// common path and bounds checking.
|
|
type Base struct {
|
|
storagedriver.StorageDriver
|
|
}
|
|
|
|
// Format errors received from the storage driver
|
|
func (base *Base) setDriverName(e error) error {
|
|
switch actual := e.(type) {
|
|
case nil:
|
|
return nil
|
|
case storagedriver.ErrUnsupportedMethod:
|
|
actual.DriverName = base.StorageDriver.Name()
|
|
return actual
|
|
case storagedriver.PathNotFoundError:
|
|
actual.DriverName = base.StorageDriver.Name()
|
|
return actual
|
|
case storagedriver.InvalidPathError:
|
|
actual.DriverName = base.StorageDriver.Name()
|
|
return actual
|
|
case storagedriver.InvalidOffsetError:
|
|
actual.DriverName = base.StorageDriver.Name()
|
|
return actual
|
|
default:
|
|
return storagedriver.Error{
|
|
DriverName: base.StorageDriver.Name(),
|
|
Detail: e,
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetContent wraps GetContent of underlying storage driver.
|
|
func (base *Base) GetContent(ctx context.Context, path string) ([]byte, error) {
|
|
ctx, done := dcontext.WithTrace(ctx)
|
|
defer done("%s.GetContent(%q)", base.Name(), path)
|
|
|
|
if !storagedriver.PathRegexp.MatchString(path) {
|
|
return nil, storagedriver.InvalidPathError{Path: path, DriverName: base.StorageDriver.Name()}
|
|
}
|
|
|
|
start := time.Now()
|
|
b, e := base.StorageDriver.GetContent(ctx, path)
|
|
storageAction.WithValues(base.Name(), "GetContent").UpdateSince(start)
|
|
return b, base.setDriverName(e)
|
|
}
|
|
|
|
// PutContent wraps PutContent of underlying storage driver.
|
|
func (base *Base) PutContent(ctx context.Context, path string, content []byte) error {
|
|
ctx, done := dcontext.WithTrace(ctx)
|
|
defer done("%s.PutContent(%q)", base.Name(), path)
|
|
|
|
if !storagedriver.PathRegexp.MatchString(path) {
|
|
return storagedriver.InvalidPathError{Path: path, DriverName: base.StorageDriver.Name()}
|
|
}
|
|
|
|
start := time.Now()
|
|
err := base.setDriverName(base.StorageDriver.PutContent(ctx, path, content))
|
|
storageAction.WithValues(base.Name(), "PutContent").UpdateSince(start)
|
|
return err
|
|
}
|
|
|
|
// Reader wraps Reader of underlying storage driver.
|
|
func (base *Base) Reader(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
|
|
ctx, done := dcontext.WithTrace(ctx)
|
|
defer done("%s.Reader(%q, %d)", base.Name(), path, offset)
|
|
|
|
if offset < 0 {
|
|
return nil, storagedriver.InvalidOffsetError{Path: path, Offset: offset, DriverName: base.StorageDriver.Name()}
|
|
}
|
|
|
|
if !storagedriver.PathRegexp.MatchString(path) {
|
|
return nil, storagedriver.InvalidPathError{Path: path, DriverName: base.StorageDriver.Name()}
|
|
}
|
|
|
|
rc, e := base.StorageDriver.Reader(ctx, path, offset)
|
|
return rc, base.setDriverName(e)
|
|
}
|
|
|
|
// Writer wraps Writer of underlying storage driver.
|
|
func (base *Base) Writer(ctx context.Context, path string, append bool) (storagedriver.FileWriter, error) {
|
|
ctx, done := dcontext.WithTrace(ctx)
|
|
defer done("%s.Writer(%q, %v)", base.Name(), path, append)
|
|
|
|
if !storagedriver.PathRegexp.MatchString(path) {
|
|
return nil, storagedriver.InvalidPathError{Path: path, DriverName: base.StorageDriver.Name()}
|
|
}
|
|
|
|
writer, e := base.StorageDriver.Writer(ctx, path, append)
|
|
return writer, base.setDriverName(e)
|
|
}
|
|
|
|
// Stat wraps Stat of underlying storage driver.
|
|
func (base *Base) Stat(ctx context.Context, path string) (storagedriver.FileInfo, error) {
|
|
ctx, done := dcontext.WithTrace(ctx)
|
|
defer done("%s.Stat(%q)", base.Name(), path)
|
|
|
|
if !storagedriver.PathRegexp.MatchString(path) && path != "/" {
|
|
return nil, storagedriver.InvalidPathError{Path: path, DriverName: base.StorageDriver.Name()}
|
|
}
|
|
|
|
start := time.Now()
|
|
fi, e := base.StorageDriver.Stat(ctx, path)
|
|
storageAction.WithValues(base.Name(), "Stat").UpdateSince(start)
|
|
return fi, base.setDriverName(e)
|
|
}
|
|
|
|
// List wraps List of underlying storage driver.
|
|
func (base *Base) List(ctx context.Context, path string) ([]string, error) {
|
|
ctx, done := dcontext.WithTrace(ctx)
|
|
defer done("%s.List(%q)", base.Name(), path)
|
|
|
|
if !storagedriver.PathRegexp.MatchString(path) && path != "/" {
|
|
return nil, storagedriver.InvalidPathError{Path: path, DriverName: base.StorageDriver.Name()}
|
|
}
|
|
|
|
start := time.Now()
|
|
str, e := base.StorageDriver.List(ctx, path)
|
|
storageAction.WithValues(base.Name(), "List").UpdateSince(start)
|
|
return str, base.setDriverName(e)
|
|
}
|
|
|
|
// Move wraps Move of underlying storage driver.
|
|
func (base *Base) Move(ctx context.Context, sourcePath string, destPath string) error {
|
|
ctx, done := dcontext.WithTrace(ctx)
|
|
defer done("%s.Move(%q, %q", base.Name(), sourcePath, destPath)
|
|
|
|
if !storagedriver.PathRegexp.MatchString(sourcePath) {
|
|
return storagedriver.InvalidPathError{Path: sourcePath, DriverName: base.StorageDriver.Name()}
|
|
} else if !storagedriver.PathRegexp.MatchString(destPath) {
|
|
return storagedriver.InvalidPathError{Path: destPath, DriverName: base.StorageDriver.Name()}
|
|
}
|
|
|
|
start := time.Now()
|
|
err := base.setDriverName(base.StorageDriver.Move(ctx, sourcePath, destPath))
|
|
storageAction.WithValues(base.Name(), "Move").UpdateSince(start)
|
|
return err
|
|
}
|
|
|
|
// Delete wraps Delete of underlying storage driver.
|
|
func (base *Base) Delete(ctx context.Context, path string) error {
|
|
ctx, done := dcontext.WithTrace(ctx)
|
|
defer done("%s.Delete(%q)", base.Name(), path)
|
|
|
|
if !storagedriver.PathRegexp.MatchString(path) {
|
|
return storagedriver.InvalidPathError{Path: path, DriverName: base.StorageDriver.Name()}
|
|
}
|
|
|
|
start := time.Now()
|
|
err := base.setDriverName(base.StorageDriver.Delete(ctx, path))
|
|
storageAction.WithValues(base.Name(), "Delete").UpdateSince(start)
|
|
return err
|
|
}
|
|
|
|
// URLFor wraps URLFor of underlying storage driver.
|
|
func (base *Base) URLFor(ctx context.Context, path string, options map[string]interface{}) (string, error) {
|
|
ctx, done := dcontext.WithTrace(ctx)
|
|
defer done("%s.URLFor(%q)", base.Name(), path)
|
|
|
|
if !storagedriver.PathRegexp.MatchString(path) {
|
|
return "", storagedriver.InvalidPathError{Path: path, DriverName: base.StorageDriver.Name()}
|
|
}
|
|
|
|
start := time.Now()
|
|
str, e := base.StorageDriver.URLFor(ctx, path, options)
|
|
storageAction.WithValues(base.Name(), "URLFor").UpdateSince(start)
|
|
return str, base.setDriverName(e)
|
|
}
|
|
|
|
// Walk wraps Walk of underlying storage driver.
|
|
func (base *Base) Walk(ctx context.Context, path string, f storagedriver.WalkFn, options ...func(*storagedriver.WalkOptions)) error {
|
|
ctx, done := dcontext.WithTrace(ctx)
|
|
defer done("%s.Walk(%q)", base.Name(), path)
|
|
|
|
if !storagedriver.PathRegexp.MatchString(path) && path != "/" {
|
|
return storagedriver.InvalidPathError{Path: path, DriverName: base.StorageDriver.Name()}
|
|
}
|
|
|
|
return base.setDriverName(base.StorageDriver.Walk(ctx, path, f, options...))
|
|
}
|