Implement Azure Files backend
Co-authored-by: moongdal <moongdal@tutanota.com>
This commit is contained in:
parent
e9763552f7
commit
b5301e03a6
10 changed files with 912 additions and 0 deletions
189
backend/azurefiles/azurefiles.go
Normal file
189
backend/azurefiles/azurefiles.go
Normal file
|
@ -0,0 +1,189 @@
|
|||
// Package azurefiles provides an interface to Microsoft Azure Files
|
||||
package azurefiles
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/directory"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/file"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/service"
|
||||
"github.com/rclone/rclone/fs"
|
||||
"github.com/rclone/rclone/fs/config"
|
||||
"github.com/rclone/rclone/fs/config/configmap"
|
||||
"github.com/rclone/rclone/fs/config/configstruct"
|
||||
"github.com/rclone/rclone/lib/encoder"
|
||||
)
|
||||
|
||||
// TODO: enable x-ms-allow-trailing-do
|
||||
// TODO: length
|
||||
// EncodeCtl | EncodeDel because del is defined as a CTL characater in section 2.2 of RFC 2616.
|
||||
var defaultEncoder = (encoder.EncodeDoubleQuote |
|
||||
encoder.EncodeBackSlash |
|
||||
encoder.EncodeSlash |
|
||||
encoder.EncodeColon |
|
||||
encoder.EncodePipe |
|
||||
encoder.EncodeLtGt |
|
||||
encoder.EncodeAsterisk |
|
||||
encoder.EncodeQuestion |
|
||||
encoder.EncodeInvalidUtf8 |
|
||||
encoder.EncodeCtl | encoder.EncodeDel |
|
||||
encoder.EncodeDot | encoder.EncodeRightPeriod)
|
||||
|
||||
func init() {
|
||||
fs.Register(&fs.RegInfo{
|
||||
Name: "azurefiles",
|
||||
Description: "Microsoft Azure Files",
|
||||
NewFs: NewFs,
|
||||
Options: []fs.Option{{
|
||||
Name: "share_name",
|
||||
Help: `Azure Files Share Name.`,
|
||||
}, {
|
||||
Name: "connection_string",
|
||||
Help: `Azure Files Connection String.`,
|
||||
}, {
|
||||
Name: "account",
|
||||
Help: `Storage Account Name.`,
|
||||
}, {
|
||||
Name: "key",
|
||||
Help: `Storage Account Shared Key.`,
|
||||
Sensitive: true,
|
||||
}, {
|
||||
Name: "sas_url",
|
||||
Help: `Shared Access Signature.
|
||||
|
||||
Works after allowing access to service, Container and Object resource types`,
|
||||
Sensitive: true,
|
||||
}, {
|
||||
Name: config.ConfigEncoding,
|
||||
Help: config.ConfigEncodingHelp,
|
||||
Advanced: true,
|
||||
Default: defaultEncoder,
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
// Options defines the configuration for this backend
|
||||
type Options struct {
|
||||
ShareName string
|
||||
ConnectionString string
|
||||
Account string
|
||||
Key string
|
||||
SASUrl string `config:"sas_url"`
|
||||
Enc encoder.MultiEncoder `config:"encoding"`
|
||||
}
|
||||
|
||||
type authenticationScheme int
|
||||
|
||||
const (
|
||||
accountAndKey authenticationScheme = iota
|
||||
connectionString
|
||||
sasURL
|
||||
)
|
||||
|
||||
func authenticationSchemeFromOptions(opt *Options) (authenticationScheme, error) {
|
||||
if opt.ConnectionString != "" {
|
||||
return connectionString, nil
|
||||
} else if opt.Account != "" && opt.Key != "" {
|
||||
return accountAndKey, nil
|
||||
} else if opt.SASUrl != "" {
|
||||
return sasURL, nil
|
||||
}
|
||||
return -1, errors.New("could not determine authentication scheme from options")
|
||||
}
|
||||
|
||||
// Factored out from NewFs so that it can be tested with opt *Options and without m configmap.Mapper
|
||||
func newFsFromOptions(ctx context.Context, name, root string, opt *Options) (fs.Fs, error) {
|
||||
as, err := authenticationSchemeFromOptions(opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var serviceClient *service.Client
|
||||
switch as {
|
||||
case connectionString:
|
||||
serviceClient, err = service.NewClientFromConnectionString(opt.ConnectionString, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case accountAndKey:
|
||||
skc, err := file.NewSharedKeyCredential(opt.Account, opt.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fileURL := fmt.Sprintf("https://%s.file.core.windows.net/%s", opt.Account, opt.ShareName)
|
||||
serviceClient, err = service.NewClientWithSharedKeyCredential(fileURL, skc, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case sasURL:
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse SAS URL: %w", err)
|
||||
}
|
||||
serviceClient, err = service.NewClientWithNoCredential(opt.SASUrl, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create SAS URL client: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
shareClient := serviceClient.NewShareClient(opt.ShareName)
|
||||
shareRootDirClient := shareClient.NewRootDirectoryClient()
|
||||
f := Fs{
|
||||
shareRootDirClient: shareRootDirClient,
|
||||
name: name,
|
||||
root: root,
|
||||
opt: opt,
|
||||
}
|
||||
// How to check whether a file exists at this location
|
||||
_, propsErr := shareRootDirClient.NewFileClient(f.opt.Enc.FromStandardPath(root)).GetProperties(ctx, nil)
|
||||
if propsErr == nil {
|
||||
f.root = path.Dir(root)
|
||||
return &f, fs.ErrorIsFile
|
||||
}
|
||||
|
||||
return &f, nil
|
||||
}
|
||||
|
||||
// NewFs constructs an Fs from the path, container:path
|
||||
//
|
||||
// TODO: what happens when root is a file
|
||||
func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, error) {
|
||||
opt := new(Options)
|
||||
err := configstruct.Set(m, opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newFsFromOptions(ctx, name, root, opt)
|
||||
}
|
||||
|
||||
var listFilesAndDirectoriesOptions = &directory.ListFilesAndDirectoriesOptions{
|
||||
Include: directory.ListFilesInclude{
|
||||
Timestamps: true,
|
||||
},
|
||||
}
|
||||
|
||||
// Fs represents a root directory inside a share. The root directory can be ""
|
||||
type Fs struct {
|
||||
shareRootDirClient *directory.Client
|
||||
name string
|
||||
root string
|
||||
opt *Options
|
||||
}
|
||||
|
||||
func (c *common) String() string {
|
||||
return c.remote
|
||||
}
|
||||
|
||||
func (c *common) Remote() string {
|
||||
return c.remote
|
||||
}
|
||||
|
||||
// TODO: implement MimeTyper
|
||||
// TODO: what heppens when update is called on Directory
|
||||
|
||||
type common struct {
|
||||
f *Fs
|
||||
remote string
|
||||
properties
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue