-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #85 from vladimirvivien/command-func-run
Starlark implementation of command function `run()`
- Loading branch information
Showing
16 changed files
with
1,079 additions
and
237 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
}, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
Oops, something went wrong.