forked from TrueCloudLab/distribution
ba4a6bbe02
Microsoft has updated the golang Azure SDK significantly. Update the azure storage driver to use the new SDK. Add support for client secret and MSI authentication schemes in addition to shared key authentication. Implement rootDirectory support for the azure storage driver to mirror the S3 driver. Signed-off-by: Kirat Singh <kirat.singh@beacon.io> Co-authored-by: Cory Snider <corhere@gmail.com>
419 lines
9.7 KiB
Go
419 lines
9.7 KiB
Go
package filesystem
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path"
|
|
"time"
|
|
|
|
storagedriver "github.com/distribution/distribution/v3/registry/storage/driver"
|
|
"github.com/distribution/distribution/v3/registry/storage/driver/base"
|
|
"github.com/distribution/distribution/v3/registry/storage/driver/factory"
|
|
)
|
|
|
|
const (
|
|
driverName = "filesystem"
|
|
defaultRootDirectory = "/var/lib/registry"
|
|
defaultMaxThreads = uint64(100)
|
|
|
|
// minThreads is the minimum value for the maxthreads configuration
|
|
// parameter. If the driver's parameters are less than this we set
|
|
// the parameters to minThreads
|
|
minThreads = uint64(25)
|
|
)
|
|
|
|
// DriverParameters represents all configuration options available for the
|
|
// filesystem driver
|
|
type DriverParameters struct {
|
|
RootDirectory string
|
|
MaxThreads uint64
|
|
}
|
|
|
|
func init() {
|
|
factory.Register(driverName, &filesystemDriverFactory{})
|
|
}
|
|
|
|
// filesystemDriverFactory implements the factory.StorageDriverFactory interface
|
|
type filesystemDriverFactory struct{}
|
|
|
|
func (factory *filesystemDriverFactory) Create(parameters map[string]interface{}) (storagedriver.StorageDriver, error) {
|
|
return FromParameters(parameters)
|
|
}
|
|
|
|
type driver struct {
|
|
rootDirectory string
|
|
}
|
|
|
|
type baseEmbed struct {
|
|
base.Base
|
|
}
|
|
|
|
// Driver is a storagedriver.StorageDriver implementation backed by a local
|
|
// filesystem. All provided paths will be subpaths of the RootDirectory.
|
|
type Driver struct {
|
|
baseEmbed
|
|
}
|
|
|
|
// FromParameters constructs a new Driver with a given parameters map
|
|
// Optional Parameters:
|
|
// - rootdirectory
|
|
// - maxthreads
|
|
func FromParameters(parameters map[string]interface{}) (*Driver, error) {
|
|
params, err := fromParametersImpl(parameters)
|
|
if err != nil || params == nil {
|
|
return nil, err
|
|
}
|
|
return New(*params), nil
|
|
}
|
|
|
|
func fromParametersImpl(parameters map[string]interface{}) (*DriverParameters, error) {
|
|
var (
|
|
err error
|
|
maxThreads = defaultMaxThreads
|
|
rootDirectory = defaultRootDirectory
|
|
)
|
|
|
|
if parameters != nil {
|
|
if rootDir, ok := parameters["rootdirectory"]; ok {
|
|
rootDirectory = fmt.Sprint(rootDir)
|
|
}
|
|
|
|
maxThreads, err = base.GetLimitFromParameter(parameters["maxthreads"], minThreads, defaultMaxThreads)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("maxthreads config error: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
params := &DriverParameters{
|
|
RootDirectory: rootDirectory,
|
|
MaxThreads: maxThreads,
|
|
}
|
|
return params, nil
|
|
}
|
|
|
|
// New constructs a new Driver with a given rootDirectory
|
|
func New(params DriverParameters) *Driver {
|
|
fsDriver := &driver{rootDirectory: params.RootDirectory}
|
|
|
|
return &Driver{
|
|
baseEmbed: baseEmbed{
|
|
Base: base.Base{
|
|
StorageDriver: base.NewRegulator(fsDriver, params.MaxThreads),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// Implement the storagedriver.StorageDriver interface
|
|
|
|
func (d *driver) Name() string {
|
|
return driverName
|
|
}
|
|
|
|
// GetContent retrieves the content stored at "path" as a []byte.
|
|
func (d *driver) GetContent(ctx context.Context, path string) ([]byte, error) {
|
|
rc, err := d.Reader(ctx, path, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rc.Close()
|
|
|
|
p, err := io.ReadAll(rc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return p, nil
|
|
}
|
|
|
|
// PutContent stores the []byte content at a location designated by "path".
|
|
func (d *driver) PutContent(ctx context.Context, subPath string, contents []byte) error {
|
|
writer, err := d.Writer(ctx, subPath, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer writer.Close()
|
|
_, err = io.Copy(writer, bytes.NewReader(contents))
|
|
if err != nil {
|
|
writer.Cancel(ctx)
|
|
return err
|
|
}
|
|
return writer.Commit()
|
|
}
|
|
|
|
// Reader retrieves an io.ReadCloser for the content stored at "path" with a
|
|
// given byte offset.
|
|
func (d *driver) Reader(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
|
|
file, err := os.OpenFile(d.fullPath(path), os.O_RDONLY, 0o644)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, storagedriver.PathNotFoundError{Path: path}
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
seekPos, err := file.Seek(offset, io.SeekStart)
|
|
if err != nil {
|
|
file.Close()
|
|
return nil, err
|
|
} else if seekPos < offset {
|
|
file.Close()
|
|
return nil, storagedriver.InvalidOffsetError{Path: path, Offset: offset}
|
|
}
|
|
|
|
return file, nil
|
|
}
|
|
|
|
func (d *driver) Writer(ctx context.Context, subPath string, append bool) (storagedriver.FileWriter, error) {
|
|
fullPath := d.fullPath(subPath)
|
|
parentDir := path.Dir(fullPath)
|
|
if err := os.MkdirAll(parentDir, 0o777); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fp, err := os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE, 0o666)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var offset int64
|
|
|
|
if !append {
|
|
err := fp.Truncate(0)
|
|
if err != nil {
|
|
fp.Close()
|
|
return nil, err
|
|
}
|
|
} else {
|
|
n, err := fp.Seek(0, io.SeekEnd)
|
|
if err != nil {
|
|
fp.Close()
|
|
return nil, err
|
|
}
|
|
offset = n
|
|
}
|
|
|
|
return newFileWriter(fp, offset), nil
|
|
}
|
|
|
|
// Stat retrieves the FileInfo for the given path, including the current size
|
|
// in bytes and the creation time.
|
|
func (d *driver) Stat(ctx context.Context, subPath string) (storagedriver.FileInfo, error) {
|
|
fullPath := d.fullPath(subPath)
|
|
|
|
fi, err := os.Stat(fullPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, storagedriver.PathNotFoundError{Path: subPath}
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
return fileInfo{
|
|
path: subPath,
|
|
FileInfo: fi,
|
|
}, nil
|
|
}
|
|
|
|
// List returns a list of the objects that are direct descendants of the given
|
|
// path.
|
|
func (d *driver) List(ctx context.Context, subPath string) ([]string, error) {
|
|
fullPath := d.fullPath(subPath)
|
|
|
|
dir, err := os.Open(fullPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, storagedriver.PathNotFoundError{Path: subPath}
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
defer dir.Close()
|
|
|
|
fileNames, err := dir.Readdirnames(0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
keys := make([]string, 0, len(fileNames))
|
|
for _, fileName := range fileNames {
|
|
keys = append(keys, path.Join(subPath, fileName))
|
|
}
|
|
|
|
return keys, nil
|
|
}
|
|
|
|
// Move moves an object stored at sourcePath to destPath, removing the original
|
|
// object.
|
|
func (d *driver) Move(ctx context.Context, sourcePath string, destPath string) error {
|
|
source := d.fullPath(sourcePath)
|
|
dest := d.fullPath(destPath)
|
|
|
|
if _, err := os.Stat(source); os.IsNotExist(err) {
|
|
return storagedriver.PathNotFoundError{Path: sourcePath}
|
|
}
|
|
|
|
if err := os.MkdirAll(path.Dir(dest), 0o777); err != nil {
|
|
return err
|
|
}
|
|
|
|
err := os.Rename(source, dest)
|
|
return err
|
|
}
|
|
|
|
// Delete recursively deletes all objects stored at "path" and its subpaths.
|
|
func (d *driver) Delete(ctx context.Context, subPath string) error {
|
|
fullPath := d.fullPath(subPath)
|
|
|
|
_, err := os.Stat(fullPath)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
} else if err != nil {
|
|
return storagedriver.PathNotFoundError{Path: subPath}
|
|
}
|
|
|
|
err = os.RemoveAll(fullPath)
|
|
return err
|
|
}
|
|
|
|
// URLFor returns a URL which may be used to retrieve the content stored at the given path.
|
|
// May return an UnsupportedMethodErr in certain StorageDriver implementations.
|
|
func (d *driver) URLFor(ctx context.Context, path string, options map[string]interface{}) (string, error) {
|
|
return "", storagedriver.ErrUnsupportedMethod{}
|
|
}
|
|
|
|
// Walk traverses a filesystem defined within driver, starting
|
|
// from the given path, calling f on each file and directory
|
|
func (d *driver) Walk(ctx context.Context, path string, f storagedriver.WalkFn) error {
|
|
return storagedriver.WalkFallback(ctx, d, path, f)
|
|
}
|
|
|
|
// fullPath returns the absolute path of a key within the Driver's storage.
|
|
func (d *driver) fullPath(subPath string) string {
|
|
return path.Join(d.rootDirectory, subPath)
|
|
}
|
|
|
|
type fileInfo struct {
|
|
os.FileInfo
|
|
path string
|
|
}
|
|
|
|
var _ storagedriver.FileInfo = fileInfo{}
|
|
|
|
// Path provides the full path of the target of this file info.
|
|
func (fi fileInfo) Path() string {
|
|
return fi.path
|
|
}
|
|
|
|
// Size returns current length in bytes of the file. The return value can
|
|
// be used to write to the end of the file at path. The value is
|
|
// meaningless if IsDir returns true.
|
|
func (fi fileInfo) Size() int64 {
|
|
if fi.IsDir() {
|
|
return 0
|
|
}
|
|
|
|
return fi.FileInfo.Size()
|
|
}
|
|
|
|
// ModTime returns the modification time for the file. For backends that
|
|
// don't have a modification time, the creation time should be returned.
|
|
func (fi fileInfo) ModTime() time.Time {
|
|
return fi.FileInfo.ModTime()
|
|
}
|
|
|
|
// IsDir returns true if the path is a directory.
|
|
func (fi fileInfo) IsDir() bool {
|
|
return fi.FileInfo.IsDir()
|
|
}
|
|
|
|
type fileWriter struct {
|
|
file *os.File
|
|
size int64
|
|
bw *bufio.Writer
|
|
closed bool
|
|
committed bool
|
|
cancelled bool
|
|
}
|
|
|
|
func newFileWriter(file *os.File, size int64) *fileWriter {
|
|
return &fileWriter{
|
|
file: file,
|
|
size: size,
|
|
bw: bufio.NewWriter(file),
|
|
}
|
|
}
|
|
|
|
func (fw *fileWriter) Write(p []byte) (int, error) {
|
|
if fw.closed {
|
|
return 0, fmt.Errorf("already closed")
|
|
} else if fw.committed {
|
|
return 0, fmt.Errorf("already committed")
|
|
} else if fw.cancelled {
|
|
return 0, fmt.Errorf("already cancelled")
|
|
}
|
|
n, err := fw.bw.Write(p)
|
|
fw.size += int64(n)
|
|
return n, err
|
|
}
|
|
|
|
func (fw *fileWriter) Size() int64 {
|
|
return fw.size
|
|
}
|
|
|
|
func (fw *fileWriter) Close() error {
|
|
if fw.closed {
|
|
return fmt.Errorf("already closed")
|
|
}
|
|
|
|
if err := fw.bw.Flush(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := fw.file.Sync(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := fw.file.Close(); err != nil {
|
|
return err
|
|
}
|
|
fw.closed = true
|
|
return nil
|
|
}
|
|
|
|
func (fw *fileWriter) Cancel(ctx context.Context) error {
|
|
if fw.closed {
|
|
return fmt.Errorf("already closed")
|
|
}
|
|
|
|
fw.cancelled = true
|
|
fw.file.Close()
|
|
return os.Remove(fw.file.Name())
|
|
}
|
|
|
|
func (fw *fileWriter) Commit() error {
|
|
if fw.closed {
|
|
return fmt.Errorf("already closed")
|
|
} else if fw.committed {
|
|
return fmt.Errorf("already committed")
|
|
} else if fw.cancelled {
|
|
return fmt.Errorf("already cancelled")
|
|
}
|
|
|
|
if err := fw.bw.Flush(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := fw.file.Sync(); err != nil {
|
|
return err
|
|
}
|
|
|
|
fw.committed = true
|
|
return nil
|
|
}
|