From d80fdad6da85e85ce6497c938131d42c1b5cccbc Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Tue, 28 Apr 2020 13:02:19 +0100 Subject: [PATCH] rc: implement backend/command for running backend commands remotely --- fs/operations/rc.go | 85 ++++++++++++++++++++++++++++++++++++++++ fs/operations/rc_test.go | 41 +++++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/fs/operations/rc.go b/fs/operations/rc.go index 3c29c3c61..346a557e0 100644 --- a/fs/operations/rc.go +++ b/fs/operations/rc.go @@ -373,3 +373,88 @@ func rcFsInfo(ctx context.Context, in rc.Params) (out rc.Params, err error) { } return out, nil } + +func init() { + rc.Add(rc.Call{ + Path: "backend/command", + AuthRequired: true, + Fn: rcBackend, + Title: "Runs a backend command.", + Help: `This takes the following parameters + +- command - a string with the command name +- fs - a remote name string eg "drive:" +- arg - a list of arguments for the backend command +- opt - a map of string to string of options + +Returns + +- result - result from the backend command + +For example + + rclone rc backend/command command=noop fs=. -o echo=yes -o blue -a path1 -a path2 + +Returns + +` + "```" + ` +{ + "result": { + "arg": [ + "path1", + "path2" + ], + "name": "noop", + "opt": { + "blue": "", + "echo": "yes" + } + } +} +` + "```" + ` + +Note that this is the direct equivalent of using this "backend" +command: + + rclone backend noop . -o echo=yes -o blue path1 path2 + +Note that arguments must be preceeded by the "-a" flag + +See the [backend](/commands/rclone_backend/) command for more information. +`, + }) +} + +// Make a public link +func rcBackend(ctx context.Context, in rc.Params) (out rc.Params, err error) { + f, err := rc.GetFs(in) + if err != nil { + return nil, err + } + doCommand := f.Features().Command + if doCommand == nil { + return nil, errors.Errorf("%v: doesn't support backend commands", f) + } + command, err := in.GetString("command") + if err != nil { + return nil, err + } + var opt = map[string]string{} + err = in.GetStructMissingOK("opt", &opt) + if err != nil { + return nil, err + } + var arg = []string{} + err = in.GetStructMissingOK("arg", &arg) + if err != nil { + return nil, err + } + result, err := doCommand(context.Background(), command, arg, opt) + if err != nil { + return nil, errors.Wrapf(err, "command %q failed", command) + + } + out = make(rc.Params) + out["result"] = result + return out, nil +} diff --git a/fs/operations/rc_test.go b/fs/operations/rc_test.go index c888509a5..36117c284 100644 --- a/fs/operations/rc_test.go +++ b/fs/operations/rc_test.go @@ -434,3 +434,44 @@ func TestRcFsInfo(t *testing.T) { assert.Equal(t, features, got["Features"]) } + +// operations/command: Runs a backend command +func TestRcCommand(t *testing.T) { + r, call := rcNewRun(t, "backend/command") + defer r.Finalise() + in := rc.Params{ + "fs": r.FremoteName, + "command": "noop", + "opt": map[string]string{ + "echo": "true", + "blue": "", + }, + "arg": []string{ + "path1", + "path2", + }, + } + got, err := call.Fn(context.Background(), in) + if err != nil { + assert.False(t, r.Fremote.Features().IsLocal, "mustn't fail on local remote") + assert.Contains(t, err.Error(), "command not found") + return + } + want := rc.Params{"result": map[string]interface{}{ + "arg": []string{ + "path1", + "path2", + }, + "name": "noop", + "opt": map[string]string{ + "blue": "", + "echo": "true", + }, + }} + assert.Equal(t, want, got) + errTxt := "explosion in the sausage factory" + in["opt"].(map[string]string)["error"] = errTxt + _, err = call.Fn(context.Background(), in) + assert.Error(t, err) + assert.Contains(t, err.Error(), errTxt) +}