mirror of
https://github.com/rclone/rclone.git
synced 2024-12-22 15:11:56 +01:00
docker serve: parse all remaining mount and VFS options
Before this change, this code implemented an ad-hoc parser for a subset of vfs and mount options. After the config re-organization it can use the same parsing code as the rest of rclone which simplifies the code and exposes all the VFS and mount options.
This commit is contained in:
parent
d8bc542ffc
commit
455b673a9f
@ -2,17 +2,15 @@ package docker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/rclone/rclone/cmd/mountlib"
|
"github.com/rclone/rclone/cmd/mountlib"
|
||||||
"github.com/rclone/rclone/fs"
|
"github.com/rclone/rclone/fs"
|
||||||
"github.com/rclone/rclone/fs/config/configmap"
|
"github.com/rclone/rclone/fs/config/configmap"
|
||||||
|
"github.com/rclone/rclone/fs/config/configstruct"
|
||||||
"github.com/rclone/rclone/fs/fspath"
|
"github.com/rclone/rclone/fs/fspath"
|
||||||
"github.com/rclone/rclone/fs/rc"
|
"github.com/rclone/rclone/fs/rc"
|
||||||
"github.com/rclone/rclone/vfs/vfscommon"
|
"github.com/rclone/rclone/vfs/vfscommon"
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// applyOptions configures volume from request options.
|
// applyOptions configures volume from request options.
|
||||||
@ -112,11 +110,15 @@ func (vol *Volume) applyOptions(volOpt VolOpts) error {
|
|||||||
for key, val := range vol.Options {
|
for key, val := range vol.Options {
|
||||||
opt[key] = val
|
opt[key] = val
|
||||||
}
|
}
|
||||||
|
mntMap := configmap.Simple{}
|
||||||
|
vfsMap := configmap.Simple{}
|
||||||
for key := range opt {
|
for key := range opt {
|
||||||
var ok bool
|
var ok bool
|
||||||
var err error
|
var err error
|
||||||
|
normalKey := normalOptName(key)
|
||||||
|
underscoreKey := strings.ReplaceAll(normalKey, "-", "_")
|
||||||
|
|
||||||
switch normalOptName(key) {
|
switch normalKey {
|
||||||
case "persist":
|
case "persist":
|
||||||
vol.persist, err = opt.GetBool(key)
|
vol.persist, err = opt.GetBool(key)
|
||||||
ok = true
|
ok = true
|
||||||
@ -129,25 +131,24 @@ func (vol *Volume) applyOptions(volOpt VolOpts) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
// try to use as a mount option in mntOpt
|
// try to use as a mount option in mntMap
|
||||||
ok, err = getMountOption(mntOpt, opt, key)
|
if mountlib.OptionsInfo.Get(underscoreKey) != nil {
|
||||||
if ok && err != nil {
|
mntMap[underscoreKey] = vol.Options[key]
|
||||||
return fmt.Errorf("cannot parse mount option %q: %w", key, err)
|
ok = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
// try as a vfs option in vfsOpt
|
// try as a vfs option in vfsMap
|
||||||
ok, err = getVFSOption(vfsOpt, opt, key)
|
if vfscommon.OptionsInfo.Get(underscoreKey) != nil {
|
||||||
if ok && err != nil {
|
vfsMap[underscoreKey] = vol.Options[key]
|
||||||
return fmt.Errorf("cannot parse vfs option %q: %w", key, err)
|
ok = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
// try as a backend option in fsOpt (backends use "_" instead of "-")
|
// try as a backend option in fsOpt (backends use "_" instead of "-")
|
||||||
optWithPrefix := strings.ReplaceAll(normalOptName(key), "-", "_")
|
fsOptName := strings.TrimPrefix(underscoreKey, fsType+"_")
|
||||||
fsOptName := strings.TrimPrefix(optWithPrefix, fsType+"_")
|
hasFsPrefix := underscoreKey != fsOptName
|
||||||
hasFsPrefix := optWithPrefix != fsOptName
|
|
||||||
if !hasFsPrefix || fsInfo.Options.Get(fsOptName) == nil {
|
if !hasFsPrefix || fsInfo.Options.Get(fsOptName) == nil {
|
||||||
fs.Logf(nil, "Option %q is not supported by backend %q", key, fsType)
|
fs.Logf(nil, "Option %q is not supported by backend %q", key, fsType)
|
||||||
return fmt.Errorf("unsupported backend option %q", key)
|
return fmt.Errorf("unsupported backend option %q", key)
|
||||||
@ -159,6 +160,18 @@ func (vol *Volume) applyOptions(volOpt VolOpts) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse VFS options
|
||||||
|
err = configstruct.Set(vfsMap, vfsOpt)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot parse vfs options: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse Mount options
|
||||||
|
err = configstruct.Set(mntMap, mntOpt)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot parse mount options: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// build remote string from fsName, fsType, fsOpt, fsPath
|
// build remote string from fsName, fsType, fsOpt, fsPath
|
||||||
colon := ":"
|
colon := ":"
|
||||||
comma := ","
|
comma := ","
|
||||||
@ -178,150 +191,6 @@ func (vol *Volume) applyOptions(volOpt VolOpts) error {
|
|||||||
return vol.validate()
|
return vol.validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMountOption(mntOpt *mountlib.Options, opt rc.Params, key string) (ok bool, err error) {
|
|
||||||
ok = true
|
|
||||||
switch normalOptName(key) {
|
|
||||||
case "debug-fuse":
|
|
||||||
mntOpt.DebugFUSE, err = opt.GetBool(key)
|
|
||||||
case "attr-timeout":
|
|
||||||
mntOpt.AttrTimeout, err = opt.GetFsDuration(key)
|
|
||||||
case "option":
|
|
||||||
mntOpt.ExtraOptions, err = getStringArray(opt, key)
|
|
||||||
case "fuse-flag":
|
|
||||||
mntOpt.ExtraFlags, err = getStringArray(opt, key)
|
|
||||||
case "daemon":
|
|
||||||
mntOpt.Daemon, err = opt.GetBool(key)
|
|
||||||
case "daemon-timeout":
|
|
||||||
mntOpt.DaemonTimeout, err = opt.GetFsDuration(key)
|
|
||||||
case "default-permissions":
|
|
||||||
mntOpt.DefaultPermissions, err = opt.GetBool(key)
|
|
||||||
case "allow-non-empty":
|
|
||||||
mntOpt.AllowNonEmpty, err = opt.GetBool(key)
|
|
||||||
case "allow-root":
|
|
||||||
mntOpt.AllowRoot, err = opt.GetBool(key)
|
|
||||||
case "allow-other":
|
|
||||||
mntOpt.AllowOther, err = opt.GetBool(key)
|
|
||||||
case "async-read":
|
|
||||||
mntOpt.AsyncRead, err = opt.GetBool(key)
|
|
||||||
case "max-read-ahead":
|
|
||||||
err = getFVarP(&mntOpt.MaxReadAhead, opt, key)
|
|
||||||
case "write-back-cache":
|
|
||||||
mntOpt.WritebackCache, err = opt.GetBool(key)
|
|
||||||
case "volname":
|
|
||||||
mntOpt.VolumeName, err = opt.GetString(key)
|
|
||||||
case "noappledouble":
|
|
||||||
mntOpt.NoAppleDouble, err = opt.GetBool(key)
|
|
||||||
case "noapplexattr":
|
|
||||||
mntOpt.NoAppleXattr, err = opt.GetBool(key)
|
|
||||||
case "network-mode":
|
|
||||||
mntOpt.NetworkMode, err = opt.GetBool(key)
|
|
||||||
default:
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getVFSOption(vfsOpt *vfscommon.Options, opt rc.Params, key string) (ok bool, err error) {
|
|
||||||
var intVal int64
|
|
||||||
ok = true
|
|
||||||
switch normalOptName(key) {
|
|
||||||
|
|
||||||
// options prefixed with "vfs-"
|
|
||||||
case "vfs-cache-mode":
|
|
||||||
err = getFVarP(&vfsOpt.CacheMode, opt, key)
|
|
||||||
case "vfs-cache-poll-interval":
|
|
||||||
vfsOpt.CachePollInterval, err = opt.GetFsDuration(key)
|
|
||||||
case "vfs-cache-max-age":
|
|
||||||
vfsOpt.CacheMaxAge, err = opt.GetFsDuration(key)
|
|
||||||
case "vfs-cache-max-size":
|
|
||||||
err = getFVarP(&vfsOpt.CacheMaxSize, opt, key)
|
|
||||||
case "vfs-read-chunk-size":
|
|
||||||
err = getFVarP(&vfsOpt.ChunkSize, opt, key)
|
|
||||||
case "vfs-read-chunk-size-limit":
|
|
||||||
err = getFVarP(&vfsOpt.ChunkSizeLimit, opt, key)
|
|
||||||
case "vfs-case-insensitive":
|
|
||||||
vfsOpt.CaseInsensitive, err = opt.GetBool(key)
|
|
||||||
case "vfs-write-wait":
|
|
||||||
vfsOpt.WriteWait, err = opt.GetFsDuration(key)
|
|
||||||
case "vfs-read-wait":
|
|
||||||
vfsOpt.ReadWait, err = opt.GetFsDuration(key)
|
|
||||||
case "vfs-write-back":
|
|
||||||
vfsOpt.WriteBack, err = opt.GetFsDuration(key)
|
|
||||||
case "vfs-read-ahead":
|
|
||||||
err = getFVarP(&vfsOpt.ReadAhead, opt, key)
|
|
||||||
case "vfs-used-is-size":
|
|
||||||
vfsOpt.UsedIsSize, err = opt.GetBool(key)
|
|
||||||
case "vfs-read-chunk-streams":
|
|
||||||
intVal, err = opt.GetInt64(key)
|
|
||||||
if err == nil {
|
|
||||||
if intVal >= 0 && intVal <= math.MaxInt {
|
|
||||||
vfsOpt.ChunkStreams = int(intVal)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("key %q (%v) overflows int", key, intVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unprefixed vfs options
|
|
||||||
case "no-modtime":
|
|
||||||
vfsOpt.NoModTime, err = opt.GetBool(key)
|
|
||||||
case "no-checksum":
|
|
||||||
vfsOpt.NoChecksum, err = opt.GetBool(key)
|
|
||||||
case "dir-cache-time":
|
|
||||||
vfsOpt.DirCacheTime, err = opt.GetFsDuration(key)
|
|
||||||
case "poll-interval":
|
|
||||||
vfsOpt.PollInterval, err = opt.GetFsDuration(key)
|
|
||||||
case "read-only":
|
|
||||||
vfsOpt.ReadOnly, err = opt.GetBool(key)
|
|
||||||
case "dir-perms":
|
|
||||||
err = getFVarP(&vfsOpt.DirPerms, opt, key)
|
|
||||||
case "file-perms":
|
|
||||||
err = getFVarP(&vfsOpt.FilePerms, opt, key)
|
|
||||||
|
|
||||||
// unprefixed unix-only vfs options
|
|
||||||
case "umask":
|
|
||||||
err = getFVarP(&vfsOpt.Umask, opt, key)
|
|
||||||
case "uid":
|
|
||||||
intVal, err = opt.GetInt64(key)
|
|
||||||
if err == nil {
|
|
||||||
if intVal >= 0 && intVal <= math.MaxUint32 {
|
|
||||||
vfsOpt.UID = uint32(intVal)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("key %q (%v) overflows uint32", key, intVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "gid":
|
|
||||||
intVal, err = opt.GetInt64(key)
|
|
||||||
if err == nil {
|
|
||||||
if intVal >= 0 && intVal <= math.MaxUint32 {
|
|
||||||
vfsOpt.UID = uint32(intVal)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("key %q (%v) overflows uint32", key, intVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// non-vfs options
|
|
||||||
default:
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFVarP(pvalue pflag.Value, opt rc.Params, key string) error {
|
|
||||||
str, err := opt.GetString(key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return pvalue.Set(str)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getStringArray(opt rc.Params, key string) ([]string, error) {
|
|
||||||
str, err := opt.GetString(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return strings.Split(str, ","), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalOptName(key string) string {
|
func normalOptName(key string) string {
|
||||||
return strings.ReplaceAll(strings.TrimPrefix(strings.ToLower(key), "--"), "_", "-")
|
return strings.ReplaceAll(strings.TrimPrefix(strings.ToLower(key), "--"), "_", "-")
|
||||||
}
|
}
|
||||||
|
75
cmd/serve/docker/options_test.go
Normal file
75
cmd/serve/docker/options_test.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rclone/rclone/cmd/mountlib"
|
||||||
|
"github.com/rclone/rclone/fs"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
_ "github.com/rclone/rclone/backend/local"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestApplyOptions(t *testing.T) {
|
||||||
|
vol := &Volume{
|
||||||
|
Name: "testName",
|
||||||
|
MountPoint: "testPath",
|
||||||
|
drv: &Driver{
|
||||||
|
root: "testRoot",
|
||||||
|
},
|
||||||
|
mnt: &mountlib.MountPoint{
|
||||||
|
MountPoint: "testPath",
|
||||||
|
},
|
||||||
|
mountReqs: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Happy path
|
||||||
|
volOpt := VolOpts{
|
||||||
|
"remote": "/tmp/docker",
|
||||||
|
"persist": "FALSE",
|
||||||
|
"mount_type": "potato",
|
||||||
|
// backend options
|
||||||
|
"--local-case-sensitive": "true",
|
||||||
|
"local_no_check_updated": "1",
|
||||||
|
// mount options
|
||||||
|
"debug-fuse": "true",
|
||||||
|
"attr_timeout": "100s",
|
||||||
|
"--async-read": "TRUE",
|
||||||
|
// vfs options
|
||||||
|
"no-modtime": "1",
|
||||||
|
"no_checksum": "true",
|
||||||
|
"--no-seek": "true",
|
||||||
|
}
|
||||||
|
err := vol.applyOptions(volOpt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
// normal options
|
||||||
|
assert.Equal(t, ":local,case_sensitive='true',no_check_updated='1':/tmp/docker", vol.fsString)
|
||||||
|
assert.Equal(t, false, vol.persist)
|
||||||
|
assert.Equal(t, "potato", vol.mountType)
|
||||||
|
// mount options
|
||||||
|
assert.Equal(t, true, vol.mnt.MountOpt.DebugFUSE)
|
||||||
|
assert.Equal(t, fs.Duration(100*time.Second), vol.mnt.MountOpt.AttrTimeout)
|
||||||
|
assert.Equal(t, true, vol.mnt.MountOpt.AsyncRead)
|
||||||
|
// vfs options
|
||||||
|
assert.Equal(t, true, vol.mnt.VFSOpt.NoModTime)
|
||||||
|
assert.Equal(t, true, vol.mnt.VFSOpt.NoChecksum)
|
||||||
|
assert.Equal(t, true, vol.mnt.VFSOpt.NoSeek)
|
||||||
|
|
||||||
|
// Check errors
|
||||||
|
err = vol.applyOptions(VolOpts{
|
||||||
|
"debug-fuse": "POTATO",
|
||||||
|
})
|
||||||
|
require.ErrorContains(t, err, "cannot parse mount options")
|
||||||
|
err = vol.applyOptions(VolOpts{
|
||||||
|
"no-modtime": "POTATO",
|
||||||
|
})
|
||||||
|
require.ErrorContains(t, err, "cannot parse vfs options")
|
||||||
|
err = vol.applyOptions(VolOpts{
|
||||||
|
"remote": "/tmp/docker",
|
||||||
|
"local_not_found": "POTATO",
|
||||||
|
})
|
||||||
|
require.ErrorContains(t, err, "unsupported backend option")
|
||||||
|
|
||||||
|
}
|
@ -209,7 +209,7 @@ but is arguably easier to parameterize in scripts.
|
|||||||
The `path` part is optional.
|
The `path` part is optional.
|
||||||
|
|
||||||
[Mount and VFS options](/commands/rclone_serve_docker/#options)
|
[Mount and VFS options](/commands/rclone_serve_docker/#options)
|
||||||
as well as [backend parameters](/flags/#backend-flags) are named
|
as well as [backend parameters](/flags/#backend) are named
|
||||||
like their twin command-line flags without the `--` CLI prefix.
|
like their twin command-line flags without the `--` CLI prefix.
|
||||||
Optionally you can use underscores instead of dashes in option names.
|
Optionally you can use underscores instead of dashes in option names.
|
||||||
For example, `--vfs-cache-mode full` becomes
|
For example, `--vfs-cache-mode full` becomes
|
||||||
|
Loading…
Reference in New Issue
Block a user