2015-02-11 02:14:23 +00:00
|
|
|
package driver
|
2014-10-21 22:02:20 +00:00
|
|
|
|
|
|
|
import (
|
2017-08-11 22:31:16 +00:00
|
|
|
"context"
|
2023-10-11 23:53:27 +00:00
|
|
|
"encoding/json"
|
2014-10-21 22:02:20 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2023-10-24 19:49:47 +00:00
|
|
|
"net/http"
|
2014-12-11 22:11:47 +00:00
|
|
|
"regexp"
|
2014-11-06 20:16:14 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2014-10-21 22:02:20 +00:00
|
|
|
)
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// Version is a string representing the storage driver version, of the form
|
|
|
|
// Major.Minor.
|
|
|
|
// The registry must accept storage drivers with equal major version and greater
|
|
|
|
// minor version, but may not be compatible with older storage driver versions.
|
2014-11-06 20:16:14 +00:00
|
|
|
type Version string
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// Major returns the major (primary) component of a version.
|
2014-11-06 20:16:14 +00:00
|
|
|
func (version Version) Major() uint {
|
replace strings.Split(N) for strings.Cut() or alternatives
Go 1.18 and up now provides a strings.Cut() which is better suited for
splitting key/value pairs (and similar constructs), and performs better:
```go
func BenchmarkSplit(b *testing.B) {
b.ReportAllocs()
data := []string{"12hello=world", "12hello=", "12=hello", "12hello"}
for i := 0; i < b.N; i++ {
for _, s := range data {
_ = strings.SplitN(s, "=", 2)[0]
}
}
}
func BenchmarkCut(b *testing.B) {
b.ReportAllocs()
data := []string{"12hello=world", "12hello=", "12=hello", "12hello"}
for i := 0; i < b.N; i++ {
for _, s := range data {
_, _, _ = strings.Cut(s, "=")
}
}
}
```
BenchmarkSplit
BenchmarkSplit-10 8244206 128.0 ns/op 128 B/op 4 allocs/op
BenchmarkCut
BenchmarkCut-10 54411998 21.80 ns/op 0 B/op 0 allocs/op
While looking at occurrences of `strings.Split()`, I also updated some for alternatives,
or added some constraints;
- for cases where an specific number of items is expected, I used `strings.SplitN()`
with a suitable limit. This prevents (theoretical) unlimited splits.
- in some cases it we were using `strings.Split()`, but _actually_ were trying to match
a prefix; for those I replaced the code to just match (and/or strip) the prefix.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-11-02 19:32:03 +00:00
|
|
|
majorPart, _, _ := strings.Cut(string(version), ".")
|
2014-11-06 20:16:14 +00:00
|
|
|
major, _ := strconv.ParseUint(majorPart, 10, 0)
|
|
|
|
return uint(major)
|
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// Minor returns the minor (secondary) component of a version.
|
2014-11-06 20:16:14 +00:00
|
|
|
func (version Version) Minor() uint {
|
replace strings.Split(N) for strings.Cut() or alternatives
Go 1.18 and up now provides a strings.Cut() which is better suited for
splitting key/value pairs (and similar constructs), and performs better:
```go
func BenchmarkSplit(b *testing.B) {
b.ReportAllocs()
data := []string{"12hello=world", "12hello=", "12=hello", "12hello"}
for i := 0; i < b.N; i++ {
for _, s := range data {
_ = strings.SplitN(s, "=", 2)[0]
}
}
}
func BenchmarkCut(b *testing.B) {
b.ReportAllocs()
data := []string{"12hello=world", "12hello=", "12=hello", "12hello"}
for i := 0; i < b.N; i++ {
for _, s := range data {
_, _, _ = strings.Cut(s, "=")
}
}
}
```
BenchmarkSplit
BenchmarkSplit-10 8244206 128.0 ns/op 128 B/op 4 allocs/op
BenchmarkCut
BenchmarkCut-10 54411998 21.80 ns/op 0 B/op 0 allocs/op
While looking at occurrences of `strings.Split()`, I also updated some for alternatives,
or added some constraints;
- for cases where an specific number of items is expected, I used `strings.SplitN()`
with a suitable limit. This prevents (theoretical) unlimited splits.
- in some cases it we were using `strings.Split()`, but _actually_ were trying to match
a prefix; for those I replaced the code to just match (and/or strip) the prefix.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-11-02 19:32:03 +00:00
|
|
|
_, minorPart, _ := strings.Cut(string(version), ".")
|
2014-11-06 20:16:14 +00:00
|
|
|
minor, _ := strconv.ParseUint(minorPart, 10, 0)
|
|
|
|
return uint(minor)
|
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// CurrentVersion is the current storage driver Version.
|
2014-11-06 20:16:14 +00:00
|
|
|
const CurrentVersion Version = "0.1"
|
|
|
|
|
2022-07-10 02:04:50 +00:00
|
|
|
// WalkOptions provides options to the walk function that may adjust its behaviour
|
|
|
|
type WalkOptions struct {
|
|
|
|
// If StartAfterHint is set, the walk may start with the first item lexographically
|
|
|
|
// after the hint, but it is not guaranteed and drivers may start the walk from the path.
|
|
|
|
StartAfterHint string
|
|
|
|
}
|
|
|
|
|
|
|
|
func WithStartAfterHint(startAfterHint string) func(*WalkOptions) {
|
|
|
|
return func(s *WalkOptions) {
|
|
|
|
s.StartAfterHint = startAfterHint
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// StorageDriver defines methods that a Storage Driver must implement for a
|
2016-03-18 23:28:42 +00:00
|
|
|
// filesystem-like key/value object storage. Storage Drivers are automatically
|
|
|
|
// registered via an internal registration mechanism, and generally created
|
2020-08-24 11:18:39 +00:00
|
|
|
// via the StorageDriverFactory interface (https://godoc.org/github.com/distribution/distribution/registry/storage/driver/factory).
|
2016-03-24 16:35:04 +00:00
|
|
|
// Please see the aforementioned factory package for example code showing how to get an instance
|
|
|
|
// of a StorageDriver
|
2014-10-21 22:02:20 +00:00
|
|
|
type StorageDriver interface {
|
2015-04-23 00:30:01 +00:00
|
|
|
// Name returns the human-readable "name" of the driver, useful in error
|
|
|
|
// messages and logging. By convention, this will just be the registration
|
|
|
|
// name, but drivers may provide other information here.
|
|
|
|
Name() string
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// GetContent retrieves the content stored at "path" as a []byte.
|
|
|
|
// This should primarily be used for small objects.
|
2015-04-27 22:58:58 +00:00
|
|
|
GetContent(ctx context.Context, path string) ([]byte, error)
|
2014-10-29 01:15:40 +00:00
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// PutContent stores the []byte content at a location designated by "path".
|
|
|
|
// This should primarily be used for small objects.
|
2015-04-27 22:58:58 +00:00
|
|
|
PutContent(ctx context.Context, path string, content []byte) error
|
2014-10-29 01:15:40 +00:00
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
// Reader retrieves an io.ReadCloser for the content stored at "path"
|
2014-11-17 23:44:07 +00:00
|
|
|
// with a given byte offset.
|
|
|
|
// May be used to resume reading a stream by providing a nonzero offset.
|
2016-02-08 22:29:21 +00:00
|
|
|
Reader(ctx context.Context, path string, offset int64) (io.ReadCloser, error)
|
2014-10-29 01:15:40 +00:00
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
// Writer returns a FileWriter which will store the content written to it
|
|
|
|
// at the location designated by "path" after the call to Commit.
|
2023-12-13 08:58:21 +00:00
|
|
|
// A path may be appended to if it has not been committed, or if the
|
|
|
|
// existing committed content is zero length.
|
|
|
|
//
|
|
|
|
// The behaviour of appending to paths with non-empty committed content is
|
|
|
|
// undefined. Specific implementations may document their own behavior.
|
2016-02-08 22:29:21 +00:00
|
|
|
Writer(ctx context.Context, path string, append bool) (FileWriter, error)
|
2014-10-29 01:15:40 +00:00
|
|
|
|
2014-12-03 05:00:42 +00:00
|
|
|
// Stat retrieves the FileInfo for the given path, including the current
|
|
|
|
// size in bytes and the creation time.
|
2015-04-27 22:58:58 +00:00
|
|
|
Stat(ctx context.Context, path string) (FileInfo, error)
|
2014-10-29 01:15:40 +00:00
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// List returns a list of the objects that are direct descendants of the
|
2022-11-02 21:05:45 +00:00
|
|
|
// given path.
|
2015-04-27 22:58:58 +00:00
|
|
|
List(ctx context.Context, path string) ([]string, error)
|
2014-10-29 01:15:40 +00:00
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// Move moves an object stored at sourcePath to destPath, removing the
|
|
|
|
// original object.
|
|
|
|
// Note: This may be no more efficient than a copy followed by a delete for
|
|
|
|
// many implementations.
|
2015-04-27 22:58:58 +00:00
|
|
|
Move(ctx context.Context, sourcePath string, destPath string) error
|
2014-10-29 01:15:40 +00:00
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// Delete recursively deletes all objects stored at "path" and its subpaths.
|
2015-04-27 22:58:58 +00:00
|
|
|
Delete(ctx context.Context, path string) error
|
2015-01-07 16:31:38 +00:00
|
|
|
|
2023-10-24 19:49:47 +00:00
|
|
|
// RedirectURL returns a URL which the client of the request r may use
|
|
|
|
// to retrieve the content stored at path. Returning the empty string
|
|
|
|
// signals that the request may not be redirected.
|
|
|
|
RedirectURL(r *http.Request, path string) (string, error)
|
2017-11-29 19:17:39 +00:00
|
|
|
|
|
|
|
// Walk traverses a filesystem defined within driver, starting
|
|
|
|
// from the given path, calling f on each file.
|
|
|
|
// If the returned error from the WalkFn is ErrSkipDir and fileInfo refers
|
|
|
|
// to a directory, the directory will not be entered and Walk
|
2022-07-10 02:04:50 +00:00
|
|
|
// will continue the traversal.
|
|
|
|
// If the returned error from the WalkFn is ErrFilledBuffer, processing stops.
|
|
|
|
Walk(ctx context.Context, path string, f WalkFn, options ...func(*WalkOptions)) error
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2016-02-08 22:29:21 +00:00
|
|
|
// FileWriter provides an abstraction for an opened writable file-like object in
|
|
|
|
// the storage backend. The FileWriter must flush all content written to it on
|
|
|
|
// the call to Close, but is only required to make its content readable on a
|
|
|
|
// call to Commit.
|
|
|
|
type FileWriter interface {
|
|
|
|
io.WriteCloser
|
|
|
|
|
|
|
|
// Size returns the number of bytes written to this FileWriter.
|
|
|
|
Size() int64
|
|
|
|
|
|
|
|
// Cancel removes any written content from this FileWriter.
|
2020-02-21 03:58:17 +00:00
|
|
|
Cancel(context.Context) error
|
2016-02-08 22:29:21 +00:00
|
|
|
|
|
|
|
// Commit flushes all content written to this FileWriter and makes it
|
|
|
|
// available for future calls to StorageDriver.GetContent and
|
|
|
|
// StorageDriver.Reader.
|
2023-10-18 09:34:10 +00:00
|
|
|
Commit(context.Context) error
|
2016-02-08 22:29:21 +00:00
|
|
|
}
|
|
|
|
|
2015-02-02 21:17:33 +00:00
|
|
|
// PathRegexp is the regular expression which each file path must match. A
|
|
|
|
// file path is absolute, beginning with a slash and containing a positive
|
|
|
|
// number of path components separated by slashes, where each component is
|
2015-12-04 19:12:32 +00:00
|
|
|
// restricted to alphanumeric characters or a period, underscore, or
|
2015-02-02 21:17:33 +00:00
|
|
|
// hyphen.
|
2015-04-07 21:14:45 +00:00
|
|
|
var PathRegexp = regexp.MustCompile(`^(/[A-Za-z0-9._-]+)+$`)
|
2014-12-11 22:11:47 +00:00
|
|
|
|
2015-03-13 02:31:41 +00:00
|
|
|
// ErrUnsupportedMethod may be returned in the case where a StorageDriver implementation does not support an optional method.
|
2015-10-02 23:19:06 +00:00
|
|
|
type ErrUnsupportedMethod struct {
|
|
|
|
DriverName string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (err ErrUnsupportedMethod) Error() string {
|
2015-11-24 22:17:25 +00:00
|
|
|
return fmt.Sprintf("%s: unsupported method", err.DriverName)
|
2015-10-02 23:19:06 +00:00
|
|
|
}
|
2015-01-07 16:31:38 +00:00
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// PathNotFoundError is returned when operating on a nonexistent path.
|
2014-10-21 22:02:20 +00:00
|
|
|
type PathNotFoundError struct {
|
2015-10-02 23:19:06 +00:00
|
|
|
Path string
|
|
|
|
DriverName string
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (err PathNotFoundError) Error() string {
|
2015-11-24 22:17:25 +00:00
|
|
|
return fmt.Sprintf("%s: Path not found: %s", err.DriverName, err.Path)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
2014-12-11 22:11:47 +00:00
|
|
|
// InvalidPathError is returned when the provided path is malformed.
|
|
|
|
type InvalidPathError struct {
|
2015-10-02 23:19:06 +00:00
|
|
|
Path string
|
|
|
|
DriverName string
|
2014-12-11 22:11:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (err InvalidPathError) Error() string {
|
2015-11-24 22:17:25 +00:00
|
|
|
return fmt.Sprintf("%s: invalid path: %s", err.DriverName, err.Path)
|
2014-12-11 22:11:47 +00:00
|
|
|
}
|
|
|
|
|
2014-11-17 23:44:07 +00:00
|
|
|
// InvalidOffsetError is returned when attempting to read or write from an
|
|
|
|
// invalid offset.
|
2014-10-21 22:02:20 +00:00
|
|
|
type InvalidOffsetError struct {
|
2015-10-02 23:19:06 +00:00
|
|
|
Path string
|
|
|
|
Offset int64
|
|
|
|
DriverName string
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (err InvalidOffsetError) Error() string {
|
2015-11-24 22:17:25 +00:00
|
|
|
return fmt.Sprintf("%s: invalid offset: %d for path: %s", err.DriverName, err.Offset, err.Path)
|
2014-10-21 22:02:20 +00:00
|
|
|
}
|
2015-11-02 21:23:53 +00:00
|
|
|
|
|
|
|
// Error is a catch-all error type which captures an error string and
|
2016-02-11 00:26:29 +00:00
|
|
|
// the driver type on which it occurred.
|
2015-11-02 21:23:53 +00:00
|
|
|
type Error struct {
|
|
|
|
DriverName string
|
2023-10-18 09:02:21 +00:00
|
|
|
Detail error
|
2015-11-02 21:23:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (err Error) Error() string {
|
2023-10-18 09:02:21 +00:00
|
|
|
return fmt.Sprintf("%s: %s", err.DriverName, err.Detail)
|
2015-11-02 21:23:53 +00:00
|
|
|
}
|
2022-04-23 11:37:09 +00:00
|
|
|
|
2023-10-11 23:53:27 +00:00
|
|
|
func (err Error) MarshalJSON() ([]byte, error) {
|
|
|
|
return json.Marshal(struct {
|
|
|
|
DriverName string `json:"driver"`
|
2023-10-18 09:02:21 +00:00
|
|
|
Detail string `json:"detail"`
|
2023-10-11 23:53:27 +00:00
|
|
|
}{
|
|
|
|
DriverName: err.DriverName,
|
2023-10-18 09:02:21 +00:00
|
|
|
Detail: err.Detail.Error(),
|
2023-10-11 23:53:27 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-04-23 11:37:09 +00:00
|
|
|
// Errors provides the envelope for multiple errors
|
|
|
|
// for use within the storagedriver implementations.
|
|
|
|
type Errors struct {
|
|
|
|
DriverName string
|
|
|
|
Errs []error
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ error = Errors{}
|
|
|
|
|
|
|
|
func (e Errors) Error() string {
|
|
|
|
switch len(e.Errs) {
|
|
|
|
case 0:
|
2023-10-18 09:02:21 +00:00
|
|
|
return fmt.Sprintf("%s: <nil>", e.DriverName)
|
2022-04-23 11:37:09 +00:00
|
|
|
case 1:
|
2023-10-18 09:02:21 +00:00
|
|
|
return fmt.Sprintf("%s: %s", e.DriverName, e.Errs[0].Error())
|
2022-04-23 11:37:09 +00:00
|
|
|
default:
|
|
|
|
msg := "errors:\n"
|
|
|
|
for _, err := range e.Errs {
|
|
|
|
msg += err.Error() + "\n"
|
|
|
|
}
|
2023-10-18 09:02:21 +00:00
|
|
|
return fmt.Sprintf("%s: %s", e.DriverName, msg)
|
2022-04-23 11:37:09 +00:00
|
|
|
}
|
|
|
|
}
|
2023-10-18 09:02:21 +00:00
|
|
|
|
|
|
|
// MarshalJSON converts slice of errors into the format
|
|
|
|
// that is serializable by JSON.
|
|
|
|
func (e Errors) MarshalJSON() ([]byte, error) {
|
|
|
|
tmpErrs := struct {
|
|
|
|
DriverName string `json:"driver"`
|
|
|
|
Details []string `json:"details"`
|
|
|
|
}{
|
|
|
|
DriverName: e.DriverName,
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(e.Errs) == 0 {
|
|
|
|
tmpErrs.Details = make([]string, 0)
|
|
|
|
return json.Marshal(tmpErrs)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, err := range e.Errs {
|
|
|
|
tmpErrs.Details = append(tmpErrs.Details, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
return json.Marshal(tmpErrs)
|
|
|
|
}
|