Skip to content

Commit

Permalink
Merge pull request #522 from nkryuchkov/feature/exec-on-visor
Browse files Browse the repository at this point in the history
Implement arbitrary command execution on visor from hypervisor
  • Loading branch information
志宇 authored Aug 23, 2019
2 parents 1aadc31 + d9fc5aa commit 381bfb1
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 0 deletions.
13 changes: 13 additions & 0 deletions cmd/skywire-cli/commands/node/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"strconv"
"strings"
"text/tabwriter"

"github.com/spf13/cobra"
Expand All @@ -18,6 +19,7 @@ func init() {
startAppCmd,
stopAppCmd,
setAppAutostartCmd,
execCmd,
)
}

Expand Down Expand Up @@ -82,3 +84,14 @@ var setAppAutostartCmd = &cobra.Command{
fmt.Println("OK")
},
}

var execCmd = &cobra.Command{
Use: "exec <command>",
Short: "Executes the given command",
Args: cobra.MinimumNArgs(1),
Run: func(_ *cobra.Command, args []string) {
out, err := rpcClient().Exec(strings.Join(args, " "))
internal.Catch(err)
fmt.Println(string(out))
},
}
25 changes: 25 additions & 0 deletions pkg/hypervisor/hypervisor.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ func (m *Node) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
r.Get("/user", m.users.UserInfo())
r.Post("/change-password", m.users.ChangePassword())
r.Post("/exec", m.exec())
r.Get("/nodes", m.getNodes())
r.Get("/nodes/{pk}", m.getNode())
r.Get("/nodes/{pk}/apps", m.getApps())
Expand All @@ -150,6 +151,30 @@ func (m *Node) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.ServeHTTP(w, req)
}

// executes a command and returns its output
func (m *Node) exec() http.HandlerFunc {
return m.withCtx(m.nodeCtx, func(w http.ResponseWriter, r *http.Request, ctx *httpCtx) {
var reqBody struct {
Command string `json:"command"`
}
if err := httputil.ReadJSON(r, &reqBody); err != nil {
httputil.WriteJSON(w, r, http.StatusBadRequest, err)
return
}

out, err := ctx.RPC.Exec(reqBody.Command)
if err != nil {
httputil.WriteJSON(w, r, http.StatusInternalServerError, err)
return
}

output := struct {
Output string `json:"output"`
}{string(out)}
httputil.WriteJSON(w, r, http.StatusOK, output)
})
}

type summaryResp struct {
TCPAddr string `json:"tcp_addr"`
*visor.Summary
Expand Down
7 changes: 7 additions & 0 deletions pkg/visor/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ func (r *RPC) Summary(_ *struct{}, out *Summary) error {
return nil
}

// Exec executes a given command in cmd and writes its output to out.
func (r *RPC) Exec(cmd *string, out *[]byte) error {
var err error
*out, err = r.node.Exec(*cmd)
return err
}

/*
<<< APP MANAGEMENT >>>
*/
Expand Down
13 changes: 13 additions & 0 deletions pkg/visor/rpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
// RPCClient represents a RPC Client implementation.
type RPCClient interface {
Summary() (*Summary, error)
Exec(command string) ([]byte, error)

Apps() ([]*AppState, error)
StartApp(appName string) error
Expand Down Expand Up @@ -64,6 +65,13 @@ func (rc *rpcClient) Summary() (*Summary, error) {
return out, err
}

// Exec calls Exec.
func (rc *rpcClient) Exec(command string) ([]byte, error) {
output := make([]byte, 0)
err := rc.Call("Exec", &command, &output)
return output, err
}

// Apps calls Apps.
func (rc *rpcClient) Apps() ([]*AppState, error) {
states := make([]*AppState, 0)
Expand Down Expand Up @@ -277,6 +285,11 @@ func (mc *mockRPCClient) Summary() (*Summary, error) {
return &out, err
}

// Exec implements RPCClient.
func (mc *mockRPCClient) Exec(command string) ([]byte, error) {
return []byte("mock"), nil
}

// Apps implements RPCClient.
func (mc *mockRPCClient) Apps() ([]*AppState, error) {
var apps []*AppState
Expand Down
16 changes: 16 additions & 0 deletions pkg/visor/rpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,22 @@ func TestRPC(t *testing.T) {
// })
})

t.Run("Exec", func(t *testing.T) {
command := "echo 1"

t.Run("RPCServer", func(t *testing.T) {
var out []byte
require.NoError(t, gateway.Exec(&command, &out))
assert.Equal(t, []byte("1\n"), out)
})

// t.Run("RPCClient", func(t *testing.T) {
// out, err := client.Exec(command)
// require.NoError(t, err)
// assert.Equal(t, []byte("1\n"), out)
// })
})

t.Run("Apps", func(t *testing.T) {
test := func(t *testing.T, apps []*AppState) {
assert.Len(t, apps, 2)
Expand Down
7 changes: 7 additions & 0 deletions pkg/visor/visor.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,13 @@ func (node *Node) Close() (err error) {
return err
}

// Exec executes a shell command. It returns combined stdout and stderr output and an error.
func (node *Node) Exec(command string) ([]byte, error) {
args := strings.Split(command, " ")
cmd := exec.Command(args[0], args[1:]...) // nolint: gosec
return cmd.CombinedOutput()
}

// Apps returns list of AppStates for all registered apps.
func (node *Node) Apps() []*AppState {
res := make([]*AppState, 0)
Expand Down

0 comments on commit 381bfb1

Please sign in to comment.