alias: add new backend to create aliases for remote names #1049
The alias backend is a wrapper for an existing remote. It allows you to name a "remote:path" as an "alias:".
This commit is contained in:
parent
5055b340da
commit
b183bd7f00
11 changed files with 289 additions and 0 deletions
45
backend/alias/alias.go
Normal file
45
backend/alias/alias.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package alias
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ncw/rclone/fs"
|
||||||
|
"github.com/ncw/rclone/fs/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register with Fs
|
||||||
|
func init() {
|
||||||
|
fsi := &fs.RegInfo{
|
||||||
|
Name: "alias",
|
||||||
|
Description: "Alias for a existing remote",
|
||||||
|
NewFs: NewFs,
|
||||||
|
Options: []fs.Option{{
|
||||||
|
Name: "remote",
|
||||||
|
Help: "Remote or path to alias.\nCan be \"myremote:path/to/dir\", \"myremote:bucket\", \"myremote:\" or \"/local/path\".",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
fs.Register(fsi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFs contstructs an Fs from the path.
|
||||||
|
//
|
||||||
|
// The returned Fs is the actual Fs, referenced by remote in the config
|
||||||
|
func NewFs(name, root string) (fs.Fs, error) {
|
||||||
|
remote := config.FileGet(name, "remote")
|
||||||
|
if remote == "" {
|
||||||
|
return nil, errors.New("alias can't point to an empty remote - check the value of the remote setting")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(remote, name+":") {
|
||||||
|
return nil, errors.New("can't point alias remote at itself - check the value of the remote setting")
|
||||||
|
}
|
||||||
|
fsInfo, configName, fsPath, err := fs.ParseRemote(remote)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
root = filepath.ToSlash(root)
|
||||||
|
return fsInfo.NewFs(configName, path.Join(fsPath, root))
|
||||||
|
}
|
106
backend/alias/alias_internal_test.go
Normal file
106
backend/alias/alias_internal_test.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package alias
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
_ "github.com/ncw/rclone/backend/local" // pull in test backend
|
||||||
|
"github.com/ncw/rclone/fs"
|
||||||
|
"github.com/ncw/rclone/fs/config"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
remoteName = "TestAlias"
|
||||||
|
testPath = "test"
|
||||||
|
filesPath = filepath.Join(testPath, "files")
|
||||||
|
)
|
||||||
|
|
||||||
|
func prepare(t *testing.T, root string) {
|
||||||
|
config.LoadConfig()
|
||||||
|
|
||||||
|
// Configure the remote
|
||||||
|
config.FileSet(remoteName, "type", "alias")
|
||||||
|
config.FileSet(remoteName, "remote", root)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewFS(t *testing.T) {
|
||||||
|
type testEntry struct {
|
||||||
|
remote string
|
||||||
|
size int64
|
||||||
|
isDir bool
|
||||||
|
}
|
||||||
|
for testi, test := range []struct {
|
||||||
|
remoteRoot string
|
||||||
|
fsRoot string
|
||||||
|
fsList string
|
||||||
|
wantOK bool
|
||||||
|
entries []testEntry
|
||||||
|
}{
|
||||||
|
{"", "", "", true, []testEntry{
|
||||||
|
{"four", -1, true},
|
||||||
|
{"one%.txt", 6, false},
|
||||||
|
{"three", -1, true},
|
||||||
|
{"two.html", 7, false},
|
||||||
|
}},
|
||||||
|
{"", "four", "", true, []testEntry{
|
||||||
|
{"five", -1, true},
|
||||||
|
{"under four.txt", 9, false},
|
||||||
|
}},
|
||||||
|
{"", "", "four", true, []testEntry{
|
||||||
|
{"four/five", -1, true},
|
||||||
|
{"four/under four.txt", 9, false},
|
||||||
|
}},
|
||||||
|
{"four", "..", "", true, []testEntry{
|
||||||
|
{"four", -1, true},
|
||||||
|
{"one%.txt", 6, false},
|
||||||
|
{"three", -1, true},
|
||||||
|
{"two.html", 7, false},
|
||||||
|
}},
|
||||||
|
{"four", "../three", "", true, []testEntry{
|
||||||
|
{"underthree.txt", 9, false},
|
||||||
|
}},
|
||||||
|
} {
|
||||||
|
what := fmt.Sprintf("test %d remoteRoot=%q, fsRoot=%q, fsList=%q", testi, test.remoteRoot, test.fsRoot, test.fsList)
|
||||||
|
|
||||||
|
remoteRoot, err := filepath.Abs(filepath.FromSlash(path.Join("test/files", test.remoteRoot)))
|
||||||
|
require.NoError(t, err, what)
|
||||||
|
prepare(t, remoteRoot)
|
||||||
|
f, err := fs.NewFs(fmt.Sprintf("%s:%s", remoteName, test.fsRoot))
|
||||||
|
require.NoError(t, err, what)
|
||||||
|
gotEntries, err := f.List(test.fsList)
|
||||||
|
require.NoError(t, err, what)
|
||||||
|
|
||||||
|
sort.Sort(gotEntries)
|
||||||
|
|
||||||
|
require.Equal(t, len(test.entries), len(gotEntries), what)
|
||||||
|
for i, gotEntry := range gotEntries {
|
||||||
|
what := fmt.Sprintf("%s, entry=%d", what, i)
|
||||||
|
wantEntry := test.entries[i]
|
||||||
|
|
||||||
|
require.Equal(t, wantEntry.remote, gotEntry.Remote(), what)
|
||||||
|
require.Equal(t, wantEntry.size, int64(gotEntry.Size()), what)
|
||||||
|
_, isDir := gotEntry.(fs.Directory)
|
||||||
|
require.Equal(t, wantEntry.isDir, isDir, what)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewFSNoRemote(t *testing.T) {
|
||||||
|
prepare(t, "")
|
||||||
|
f, err := fs.NewFs(fmt.Sprintf("%s:", remoteName))
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Nil(t, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewFSInvalidRemote(t *testing.T) {
|
||||||
|
prepare(t, "not_existing_test_remote:")
|
||||||
|
f, err := fs.NewFs(fmt.Sprintf("%s:", remoteName))
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Nil(t, f)
|
||||||
|
}
|
1
backend/alias/test/files/four/five/underfive.txt
Normal file
1
backend/alias/test/files/four/five/underfive.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
apple
|
1
backend/alias/test/files/four/under four.txt
Normal file
1
backend/alias/test/files/four/under four.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
beetroot
|
1
backend/alias/test/files/one%.txt
Normal file
1
backend/alias/test/files/one%.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
hello
|
1
backend/alias/test/files/three/underthree.txt
Normal file
1
backend/alias/test/files/three/underthree.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
rutabaga
|
1
backend/alias/test/files/two.html
Normal file
1
backend/alias/test/files/two.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
potato
|
|
@ -2,6 +2,7 @@ package all
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// Active file systems
|
// Active file systems
|
||||||
|
_ "github.com/ncw/rclone/backend/alias"
|
||||||
_ "github.com/ncw/rclone/backend/amazonclouddrive"
|
_ "github.com/ncw/rclone/backend/amazonclouddrive"
|
||||||
_ "github.com/ncw/rclone/backend/azureblob"
|
_ "github.com/ncw/rclone/backend/azureblob"
|
||||||
_ "github.com/ncw/rclone/backend/b2"
|
_ "github.com/ncw/rclone/backend/b2"
|
||||||
|
|
|
@ -21,6 +21,7 @@ docs = [
|
||||||
"overview.md",
|
"overview.md",
|
||||||
|
|
||||||
# Keep these alphabetical by full name
|
# Keep these alphabetical by full name
|
||||||
|
"alias.md",
|
||||||
"amazonclouddrive.md",
|
"amazonclouddrive.md",
|
||||||
"s3.md",
|
"s3.md",
|
||||||
"b2.md",
|
"b2.md",
|
||||||
|
|
130
docs/content/alias.md
Normal file
130
docs/content/alias.md
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
---
|
||||||
|
title: "Alias"
|
||||||
|
description: "Remote Aliases"
|
||||||
|
date: "2018-01-30"
|
||||||
|
---
|
||||||
|
|
||||||
|
<i class="fa fa-link"></i> Alias
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
The `alias` remote provides a new name for another remote.
|
||||||
|
|
||||||
|
Paths may be as deep as required or a local path,
|
||||||
|
eg `remote:directory/subdirectory` or `/directory/subdirectory`.
|
||||||
|
|
||||||
|
During the initial setup with `rclone config` you will specify the target
|
||||||
|
remote. The target remote can either be a local path or another remote.
|
||||||
|
|
||||||
|
Subfolders can be used in target remote. Asume a alias remote named `backup`
|
||||||
|
with the target `mydrive:private/backup`. Invoking `rclone mkdir backup:desktop`
|
||||||
|
is exactly the same as invoking `rclone mkdir mydrive:private/backup/desktop`.
|
||||||
|
|
||||||
|
There will be no special handling of paths containing `..` segments.
|
||||||
|
Invoking `rclone mkdir backup:../desktop` is exactly the same as invoking
|
||||||
|
`rclone mkdir mydrive:private/backup/../desktop`.
|
||||||
|
The empty path is not allowed as a remote. To alias the current directory
|
||||||
|
use `.` instead.
|
||||||
|
|
||||||
|
Here is an example of how to make a alias called `remote` for local folder.
|
||||||
|
First run:
|
||||||
|
|
||||||
|
rclone config
|
||||||
|
|
||||||
|
This will guide you through an interactive setup process:
|
||||||
|
|
||||||
|
```
|
||||||
|
No remotes found - make a new one
|
||||||
|
n) New remote
|
||||||
|
s) Set configuration password
|
||||||
|
q) Quit config
|
||||||
|
n/s/q> n
|
||||||
|
name> remote
|
||||||
|
Type of storage to configure.
|
||||||
|
Choose a number from below, or type in your own value
|
||||||
|
1 / Alias for a existing remote
|
||||||
|
\ "alias"
|
||||||
|
2 / Amazon Drive
|
||||||
|
\ "amazon cloud drive"
|
||||||
|
3 / Amazon S3 (also Dreamhost, Ceph, Minio)
|
||||||
|
\ "s3"
|
||||||
|
4 / Backblaze B2
|
||||||
|
\ "b2"
|
||||||
|
5 / Box
|
||||||
|
\ "box"
|
||||||
|
6 / Cache a remote
|
||||||
|
\ "cache"
|
||||||
|
7 / Dropbox
|
||||||
|
\ "dropbox"
|
||||||
|
8 / Encrypt/Decrypt a remote
|
||||||
|
\ "crypt"
|
||||||
|
9 / FTP Connection
|
||||||
|
\ "ftp"
|
||||||
|
10 / Google Cloud Storage (this is not Google Drive)
|
||||||
|
\ "google cloud storage"
|
||||||
|
11 / Google Drive
|
||||||
|
\ "drive"
|
||||||
|
12 / Hubic
|
||||||
|
\ "hubic"
|
||||||
|
13 / Local Disk
|
||||||
|
\ "local"
|
||||||
|
14 / Microsoft Azure Blob Storage
|
||||||
|
\ "azureblob"
|
||||||
|
15 / Microsoft OneDrive
|
||||||
|
\ "onedrive"
|
||||||
|
16 / Openstack Swift (Rackspace Cloud Files, Memset Memstore, OVH)
|
||||||
|
\ "swift"
|
||||||
|
17 / Pcloud
|
||||||
|
\ "pcloud"
|
||||||
|
18 / QingCloud Object Storage
|
||||||
|
\ "qingstor"
|
||||||
|
19 / SSH/SFTP Connection
|
||||||
|
\ "sftp"
|
||||||
|
20 / Webdav
|
||||||
|
\ "webdav"
|
||||||
|
21 / Yandex Disk
|
||||||
|
\ "yandex"
|
||||||
|
22 / http Connection
|
||||||
|
\ "http"
|
||||||
|
Storage> 1
|
||||||
|
Remote or path to alias.
|
||||||
|
Can be "myremote:path/to/dir", "myremote:bucket", "myremote:" or "/local/path".
|
||||||
|
remote> /mnt/storage/backup
|
||||||
|
Remote config
|
||||||
|
--------------------
|
||||||
|
[remote]
|
||||||
|
remote = /mnt/storage/backup
|
||||||
|
--------------------
|
||||||
|
y) Yes this is OK
|
||||||
|
e) Edit this remote
|
||||||
|
d) Delete this remote
|
||||||
|
y/e/d> y
|
||||||
|
Current remotes:
|
||||||
|
|
||||||
|
Name Type
|
||||||
|
==== ====
|
||||||
|
remote alias
|
||||||
|
|
||||||
|
e) Edit existing remote
|
||||||
|
n) New remote
|
||||||
|
d) Delete remote
|
||||||
|
r) Rename remote
|
||||||
|
c) Copy remote
|
||||||
|
s) Set configuration password
|
||||||
|
q) Quit config
|
||||||
|
e/n/d/r/c/s/q> q
|
||||||
|
```
|
||||||
|
|
||||||
|
Once configured you can then use `rclone` like this,
|
||||||
|
|
||||||
|
List directories in top level in `/mnt/storage/backup`
|
||||||
|
|
||||||
|
rclone lsd remote:
|
||||||
|
|
||||||
|
List all the files in `/mnt/storage/backup`
|
||||||
|
|
||||||
|
rclone ls remote:
|
||||||
|
|
||||||
|
Copy another local directory to the alias directory called source
|
||||||
|
|
||||||
|
rclone copy /home/source remote:source
|
||||||
|
|
|
@ -19,6 +19,7 @@ option:
|
||||||
|
|
||||||
See the following for detailed instructions for
|
See the following for detailed instructions for
|
||||||
|
|
||||||
|
* [Alias](/alias/)
|
||||||
* [Amazon Drive](/amazonclouddrive/)
|
* [Amazon Drive](/amazonclouddrive/)
|
||||||
* [Amazon S3](/s3/)
|
* [Amazon S3](/s3/)
|
||||||
* [Backblaze B2](/b2/)
|
* [Backblaze B2](/b2/)
|
||||||
|
|
Loading…
Reference in a new issue