rc: fix core/command giving 500 internal error - fixes #4914
Before this change calling core/command gave the error error: response object is required expecting *http.ResponseWriter value for key "_response" (was *http.response) This was because the http.ResponseWriter is an interface not an object. Removing the `*` fixes the problem. This also indicates that this bit of code wasn't properly tested.
This commit is contained in:
parent
35ccfe1721
commit
b3710c962e
4 changed files with 125 additions and 21 deletions
|
@ -353,17 +353,22 @@ func init() {
|
||||||
- command - a string with the command name
|
- command - a string with the command name
|
||||||
- arg - a list of arguments for the backend command
|
- arg - a list of arguments for the backend command
|
||||||
- opt - a map of string to string of options
|
- opt - a map of string to string of options
|
||||||
|
- returnType - one of ("COMBINED_OUTPUT", "STREAM", "STREAM_ONLY_STDOUT", "STREAM_ONLY_STDERR")
|
||||||
|
- defaults to "COMBINED_OUTPUT" if not set
|
||||||
|
- the STREAM returnTypes will write the output to the body of the HTTP message
|
||||||
|
- the COMBINED_OUTPUT will write the output to the "result" parameter
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
|
|
||||||
- result - result from the backend command
|
- result - result from the backend command
|
||||||
|
- only set when using returnType "COMBINED_OUTPUT"
|
||||||
- error - set if rclone exits with an error code
|
- error - set if rclone exits with an error code
|
||||||
- returnType - one of ("COMBINED_OUTPUT", "STREAM", "STREAM_ONLY_STDOUT". "STREAM_ONLY_STDERR")
|
- returnType - one of ("COMBINED_OUTPUT", "STREAM", "STREAM_ONLY_STDOUT", "STREAM_ONLY_STDERR")
|
||||||
|
|
||||||
For example
|
For example
|
||||||
|
|
||||||
rclone rc core/command command=ls -a mydrive:/ -o max-depth=1
|
rclone rc core/command command=ls -a mydrive:/ -o max-depth=1
|
||||||
rclone rc core/command -a ls -a mydrive:/ -o max-depth=1
|
rclone rc core/command -a ls -a mydrive:/ -o max-depth=1
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
|
|
||||||
|
@ -386,7 +391,6 @@ OR
|
||||||
|
|
||||||
// rcRunCommand runs an rclone command with the given args and flags
|
// rcRunCommand runs an rclone command with the given args and flags
|
||||||
func rcRunCommand(ctx context.Context, in Params) (out Params, err error) {
|
func rcRunCommand(ctx context.Context, in Params) (out Params, err error) {
|
||||||
|
|
||||||
command, err := in.GetString("command")
|
command, err := in.GetString("command")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
command = ""
|
command = ""
|
||||||
|
@ -409,7 +413,7 @@ func rcRunCommand(ctx context.Context, in Params) (out Params, err error) {
|
||||||
returnType = "COMBINED_OUTPUT"
|
returnType = "COMBINED_OUTPUT"
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpResponse *http.ResponseWriter
|
var httpResponse http.ResponseWriter
|
||||||
httpResponse, err = in.GetHTTPResponseWriter()
|
httpResponse, err = in.GetHTTPResponseWriter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Errorf("response object is required\n" + err.Error())
|
return nil, errors.Errorf("response object is required\n" + err.Error())
|
||||||
|
@ -460,12 +464,14 @@ func rcRunCommand(ctx context.Context, in Params) (out Params, err error) {
|
||||||
"error": false,
|
"error": false,
|
||||||
}, nil
|
}, nil
|
||||||
} else if returnType == "STREAM_ONLY_STDOUT" {
|
} else if returnType == "STREAM_ONLY_STDOUT" {
|
||||||
cmd.Stdout = *httpResponse
|
cmd.Stdout = httpResponse
|
||||||
} else if returnType == "STREAM_ONLY_STDERR" {
|
} else if returnType == "STREAM_ONLY_STDERR" {
|
||||||
cmd.Stderr = *httpResponse
|
cmd.Stderr = httpResponse
|
||||||
} else if returnType == "STREAM" {
|
} else if returnType == "STREAM" {
|
||||||
cmd.Stdout = *httpResponse
|
cmd.Stdout = httpResponse
|
||||||
cmd.Stderr = *httpResponse
|
cmd.Stderr = httpResponse
|
||||||
|
} else {
|
||||||
|
return nil, errors.Errorf("Unknown returnType %q", returnType)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -22,6 +23,12 @@ func TestMain(m *testing.M) {
|
||||||
fmt.Printf("rclone %s\n", fs.Version)
|
fmt.Printf("rclone %s\n", fs.Version)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
// Pretend to error if we have an unknown command
|
||||||
|
if os.Args[len(os.Args)-1] == "unknown_command" {
|
||||||
|
fmt.Printf("rclone %s\n", fs.Version)
|
||||||
|
fmt.Fprintf(os.Stderr, "Unknown command\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
os.Exit(m.Run())
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,17 +143,56 @@ func TestCoreQuit(t *testing.T) {
|
||||||
func TestCoreCommand(t *testing.T) {
|
func TestCoreCommand(t *testing.T) {
|
||||||
call := Calls.Get("core/command")
|
call := Calls.Get("core/command")
|
||||||
|
|
||||||
var httpResponse http.ResponseWriter = httptest.NewRecorder()
|
test := func(command string, returnType string, wantOutput string, fail bool) {
|
||||||
|
var rec = httptest.NewRecorder()
|
||||||
|
var w http.ResponseWriter = rec
|
||||||
|
|
||||||
in := Params{
|
in := Params{
|
||||||
"command": "version",
|
"command": command,
|
||||||
"opt": map[string]string{},
|
"opt": map[string]string{},
|
||||||
"arg": []string{},
|
"arg": []string{},
|
||||||
"_response": &httpResponse,
|
"_response": w,
|
||||||
|
}
|
||||||
|
if returnType != "" {
|
||||||
|
in["returnType"] = returnType
|
||||||
|
} else {
|
||||||
|
returnType = "COMBINED_OUTPUT"
|
||||||
|
}
|
||||||
|
stream := strings.HasPrefix(returnType, "STREAM")
|
||||||
|
got, err := call.Fn(context.Background(), in)
|
||||||
|
if stream && fail {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !stream {
|
||||||
|
assert.Equal(t, wantOutput, got["result"])
|
||||||
|
assert.Equal(t, fail, got["error"])
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, wantOutput, rec.Body.String())
|
||||||
|
}
|
||||||
|
assert.Equal(t, http.StatusOK, rec.Result().StatusCode)
|
||||||
}
|
}
|
||||||
got, err := call.Fn(context.Background(), in)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, fmt.Sprintf("rclone %s\n", fs.Version), got["result"])
|
version := fmt.Sprintf("rclone %s\n", fs.Version)
|
||||||
assert.Equal(t, false, got["error"])
|
errorString := "Unknown command\n"
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
test("version", "", version, false)
|
||||||
|
})
|
||||||
|
t.Run("Fail", func(t *testing.T) {
|
||||||
|
test("unknown_command", "", version+errorString, true)
|
||||||
|
})
|
||||||
|
t.Run("Combined", func(t *testing.T) {
|
||||||
|
test("unknown_command", "COMBINED_OUTPUT", version+errorString, true)
|
||||||
|
})
|
||||||
|
t.Run("Stderr", func(t *testing.T) {
|
||||||
|
test("unknown_command", "STREAM_ONLY_STDERR", errorString, true)
|
||||||
|
})
|
||||||
|
t.Run("Stdout", func(t *testing.T) {
|
||||||
|
test("unknown_command", "STREAM_ONLY_STDOUT", version, true)
|
||||||
|
})
|
||||||
|
t.Run("Stream", func(t *testing.T) {
|
||||||
|
test("unknown_command", "STREAM", version+errorString, true)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,15 +112,15 @@ func (p Params) GetHTTPRequest() (*http.Request, error) {
|
||||||
//
|
//
|
||||||
// If the parameter isn't found then error will be of type
|
// If the parameter isn't found then error will be of type
|
||||||
// ErrParamNotFound and the returned value will be nil.
|
// ErrParamNotFound and the returned value will be nil.
|
||||||
func (p Params) GetHTTPResponseWriter() (*http.ResponseWriter, error) {
|
func (p Params) GetHTTPResponseWriter() (http.ResponseWriter, error) {
|
||||||
key := "_response"
|
key := "_response"
|
||||||
value, err := p.Get(key)
|
value, err := p.Get(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
request, ok := value.(*http.ResponseWriter)
|
request, ok := value.(http.ResponseWriter)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrParamInvalid{errors.Errorf("expecting *http.ResponseWriter value for key %q (was %T)", key, value)}
|
return nil, ErrParamInvalid{errors.Errorf("expecting http.ResponseWriter value for key %q (was %T)", key, value)}
|
||||||
}
|
}
|
||||||
return request, nil
|
return request, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ package rc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -346,3 +348,53 @@ func TestParamsGetStructMissingOK(t *testing.T) {
|
||||||
assert.Equal(t, 4.2, out.Float)
|
assert.Equal(t, 4.2, out.Float)
|
||||||
assert.Equal(t, true, IsErrParamInvalid(e3), e3.Error())
|
assert.Equal(t, true, IsErrParamInvalid(e3), e3.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParamsGetHTTPRequest(t *testing.T) {
|
||||||
|
in := Params{}
|
||||||
|
req, err := in.GetHTTPRequest()
|
||||||
|
assert.Nil(t, req)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, true, IsErrParamNotFound(err), err.Error())
|
||||||
|
|
||||||
|
in = Params{
|
||||||
|
"_request": 42,
|
||||||
|
}
|
||||||
|
req, err = in.GetHTTPRequest()
|
||||||
|
assert.Nil(t, req)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, true, IsErrParamInvalid(err), err.Error())
|
||||||
|
|
||||||
|
r := new(http.Request)
|
||||||
|
in = Params{
|
||||||
|
"_request": r,
|
||||||
|
}
|
||||||
|
req, err = in.GetHTTPRequest()
|
||||||
|
assert.NotNil(t, req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, r, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamsGetHTTPResponseWriter(t *testing.T) {
|
||||||
|
in := Params{}
|
||||||
|
wr, err := in.GetHTTPResponseWriter()
|
||||||
|
assert.Nil(t, wr)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, true, IsErrParamNotFound(err), err.Error())
|
||||||
|
|
||||||
|
in = Params{
|
||||||
|
"_response": 42,
|
||||||
|
}
|
||||||
|
wr, err = in.GetHTTPResponseWriter()
|
||||||
|
assert.Nil(t, wr)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, true, IsErrParamInvalid(err), err.Error())
|
||||||
|
|
||||||
|
var w http.ResponseWriter = httptest.NewRecorder()
|
||||||
|
in = Params{
|
||||||
|
"_response": w,
|
||||||
|
}
|
||||||
|
wr, err = in.GetHTTPResponseWriter()
|
||||||
|
assert.NotNil(t, wr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, w, wr)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue