Skip to content

Commit

Permalink
Implementation of run starlark function
Browse files Browse the repository at this point in the history
This patch implements the Go code for starlark builtin function run(). The
function allows Crashd script to execute commands on specified compute resources.
This patch also does the followings:
- Add tests for run
- Updates test harness for e2e tests
- Disable tests for packages exec, script, and parser

Signed-off-by: Vladimir Vivien <vivienv@vmware.com>
  • Loading branch information
vladimirvivien committed Jun 24, 2020
1 parent f639bed commit 1dda5e6
Show file tree
Hide file tree
Showing 16 changed files with 1,079 additions and 237 deletions.
39 changes: 20 additions & 19 deletions exec/executor_test.go
Expand Up @@ -13,7 +13,6 @@ import (
"strings"
"testing"

"github.com/sirupsen/logrus"
"github.com/vmware-tanzu/crash-diagnostics/parser"
"github.com/vmware-tanzu/crash-diagnostics/script"
"github.com/vmware-tanzu/crash-diagnostics/ssh"
Expand All @@ -26,24 +25,26 @@ const (

func TestMain(m *testing.M) {
testcrashd.Init()

sshSvr := testcrashd.NewSSHServer("test-sshd-exec", testSSHPort)
logrus.Debug("Attempting to start SSH server")
if err := sshSvr.Start(); err != nil {
logrus.Error(err)
os.Exit(1)
}

testResult := m.Run()

logrus.Debug("Stopping SSH server...")
if err := sshSvr.Stop(); err != nil {
logrus.Error(err)
os.Exit(1)
}

os.Exit(testResult)

//
//sshSvr := testcrashd.NewSSHServer("test-sshd-exec", testSSHPort)
//logrus.Debug("Attempting to start SSH server")
//if err := sshSvr.Start(); err != nil {
// logrus.Error(err)
// os.Exit(1)
//}
//
//testResult := m.Run()
//
//logrus.Debug("Stopping SSH server...")
//if err := sshSvr.Stop(); err != nil {
// logrus.Error(err)
// os.Exit(1)
//}
//
//os.Exit(testResult)

// Skipping all tests
os.Exit(0)
}

type execTest struct {
Expand Down
6 changes: 3 additions & 3 deletions parser/parser_test.go
Expand Up @@ -8,12 +8,12 @@ import (
"testing"

"github.com/vmware-tanzu/crash-diagnostics/script"
testcrashd "github.com/vmware-tanzu/crash-diagnostics/testing"
)

func TestMain(m *testing.M) {
testcrashd.Init()
os.Exit(m.Run())
//testcrashd.Init()
//os.Exit(m.Run())
os.Exit(0)
}

type parserTest struct {
Expand Down
7 changes: 3 additions & 4 deletions script/support_test.go
Expand Up @@ -6,13 +6,12 @@ package script
import (
"os"
"testing"

testcrashd "github.com/vmware-tanzu/crash-diagnostics/testing"
)

func TestMain(m *testing.M) {
testcrashd.Init()
os.Exit(m.Run())
//testcrashd.Init()
//os.Exit(m.Run())
os.Exit(0)
}

type commandTest struct {
Expand Down
112 changes: 112 additions & 0 deletions ssh/ssh_run.go
@@ -0,0 +1,112 @@
// Copyright (c) 2020 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package ssh

import (
"fmt"
"time"

"github.com/sirupsen/logrus"
"github.com/vladimirvivien/echo"
"k8s.io/apimachinery/pkg/util/wait"
)

type JumpProxyArg struct {
User string
Host string
}

type SSHArgs struct {
User string
Host string
PrivateKeyPath string
Port string
MaxRetries int
JumpProxy *JumpProxyArg
}

func Run(args SSHArgs, cmd string) (string, error) {
e := echo.New()
sshCmd, err := makeSSHCmdStr(args)
if err != nil {
return "", err
}
effectiveCmd := fmt.Sprintf(`%s "%s"`, sshCmd, cmd)
logrus.Debug("ssh.Run: ", effectiveCmd)

var result string
maxRetries := args.MaxRetries
if maxRetries == 0 {
maxRetries = 10
}
retries := wait.Backoff{Steps: maxRetries, Duration: time.Millisecond * 80, Jitter: 0.1}
if err := wait.ExponentialBackoff(retries, func() (bool, error) {
p := e.RunProc(effectiveCmd)
if p.Err() != nil {
logrus.Warn(fmt.Sprintf("unable to connect: %s", p.Err()))
return false, nil
}
result = p.Result()
return true, nil // worked
}); err != nil {
logrus.Debugf("ssh.Run failed after %d tries", maxRetries)
return "", err
}

return result, nil
}

func SSHCapture(args SSHArgs, cmd string, path string) error {
return nil
}

func makeSSHCmdStr(args SSHArgs) (string, error) {
if args.User == "" {
return "", fmt.Errorf("SSH: user is required")
}
if args.Host == "" {
return "", fmt.Errorf("SSH: host is required")
}

if args.JumpProxy != nil {
if args.JumpProxy.User == "" || args.JumpProxy.Host == "" {
return "", fmt.Errorf("SSH: jump user and host are required")
}
}

sshCmdPrefix := func() string {
if logrus.GetLevel() == logrus.DebugLevel {
return "ssh -q -o StrictHostKeyChecking=no -v"
}
return "ssh -q -o StrictHostKeyChecking=no"
}

pkPath := func() string {
if args.PrivateKeyPath != "" {
return fmt.Sprintf("-i %s", args.PrivateKeyPath)
}
return ""
}

port := func() string {
if args.Port == "" {
return "-p 22"
}
return fmt.Sprintf("-p %s", args.Port)
}

jumpProxy := func() string {
if args.JumpProxy != nil {
return fmt.Sprintf("-J %s@%s", args.JumpProxy.User, args.JumpProxy.Host)
}
return ""
}
// build command as
// ssh -i <pkpath> -P <port> -J <jumpproxy> user@host
cmd := fmt.Sprintf(
`%s %s %s %s %s@%s`,
sshCmdPrefix(), pkPath(), port(), jumpProxy(), args.User, args.Host,
)
return cmd, nil
}
98 changes: 98 additions & 0 deletions ssh/ssh_run_test.go
@@ -0,0 +1,98 @@
// Copyright (c) 2020 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package ssh

import (
"os"
"os/user"
"path/filepath"
"strings"
"testing"
)

func TestSSHRun(t *testing.T) {
homeDir, err := os.UserHomeDir()
if err != nil {
t.Fatal(err)
}

usr, err := user.Current()
if err != nil {
t.Fatal(err)
}
pkPath := filepath.Join(homeDir, ".ssh/id_rsa")

tests := []struct {
name string
args SSHArgs
cmd string
result string
}{
{
name: "simple cmd",
args: SSHArgs{User: usr.Username, PrivateKeyPath: pkPath, Host: "127.0.0.1", Port: testSSHPort, MaxRetries: 100},
cmd: "echo 'Hello World!'",
result: "Hello World!",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
expected, err := Run(test.args, test.cmd)
if err != nil {
t.Fatal(err)
}
if test.result != expected {
t.Fatalf("unexpected result %s", expected)
}
})
}
}

func TestSSHRunMakeCmdStr(t *testing.T) {
tests := []struct {
name string
args SSHArgs
cmdStr string
shouldFail bool
}{
{
name: "user and host",
args: SSHArgs{User: "sshuser", Host: "local.host"},
cmdStr: "ssh -q -o StrictHostKeyChecking=no -p 22 sshuser@local.host",
},
{
name: "user host and pkpath",
args: SSHArgs{User: "sshuser", Host: "local.host", PrivateKeyPath: "/pk/path"},
cmdStr: "ssh -q -o StrictHostKeyChecking=no -i /pk/path -p 22 sshuser@local.host",
},
{
name: "user host pkpath and proxy",
args: SSHArgs{User: "sshuser", Host: "local.host", PrivateKeyPath: "/pk/path", JumpProxy: &JumpProxyArg{User: "juser", Host: "jhost"}},
cmdStr: "ssh -q -o StrictHostKeyChecking=no -i /pk/path -p 22 -J juser@jhost sshuser@local.host",
},
{
name: "missing host",
args: SSHArgs{User: "sshuser"},
shouldFail: true,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result, err := makeSSHCmdStr(test.args)
if err != nil && !test.shouldFail {
t.Fatal(err)
}
cmdFields := strings.Fields(test.cmdStr)
resultFields := strings.Fields(result)

for i := range cmdFields {
if cmdFields[i] != resultFields[i] {
t.Fatalf("unexpected command string element: %s vs. %s", cmdFields, resultFields)
}
}
})
}
}
40 changes: 40 additions & 0 deletions starlark/main_test.go
@@ -0,0 +1,40 @@
// Copyright (c) 2020 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package starlark

import (
"os"
"testing"

"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"

testcrashd "github.com/vmware-tanzu/crash-diagnostics/testing"
)

func TestMain(m *testing.M) {
testcrashd.Init()
os.Exit(m.Run())
}

func makeTestSSHConfig(pkPath, port string) *starlarkstruct.Struct {
return starlarkstruct.FromStringDict(starlarkstruct.Default, starlark.StringDict{
identifiers.username: starlark.String(getUsername()),
identifiers.port: starlark.String(port),
identifiers.privateKeyPath: starlark.String(pkPath),
})
}

func makeTestSSHHostResource(addr string, sshCfg *starlarkstruct.Struct) *starlarkstruct.Struct {
return starlarkstruct.FromStringDict(
starlarkstruct.Default,
starlark.StringDict{
"kind": starlark.String(identifiers.hostResource),
"provider": starlark.String(identifiers.hostListProvider),
"host": starlark.String(addr),
"transport": starlark.String("ssh"),
"ssh_config": sshCfg,
},
)
}
36 changes: 36 additions & 0 deletions starlark/os_builtins.go
@@ -0,0 +1,36 @@
// Copyright (c) 2020 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package starlark

import (
"fmt"
"os"
"runtime"

"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"
)

func setupOSStruct() *starlarkstruct.Struct {
return starlarkstruct.FromStringDict(starlarkstruct.Default,
starlark.StringDict{
"name": starlark.String(runtime.GOOS),
"username": starlark.String(getUsername()),
"homedir": starlark.String(os.Getenv("HOME")),
"getenv": starlark.NewBuiltin("getenv", getEnvFunc),
},
)
}

func getEnvFunc(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
if args == nil || args.Len() == 0 {
return starlark.None, nil
}
key, ok := args.Index(0).(starlark.String)
if !ok {
return starlark.None, fmt.Errorf("os.getenv: invalid env key")
}

return starlark.String(os.Getenv(string(key))), nil
}

0 comments on commit 1dda5e6

Please sign in to comment.