diff --git a/assets/platforms/cumulus_linux.yaml b/assets/platforms/cumulus_linux.yaml new file mode 100644 index 0000000..148e0b9 --- /dev/null +++ b/assets/platforms/cumulus_linux.yaml @@ -0,0 +1,66 @@ +--- +platform-type: 'cumulus_linux' +default: + driver-type: "network" + privilege-levels: + exec: + name: "exec" + pattern: '(?im)^\S+@\S+:\S+:\S+\$\s*$' + previous-priv: + deescalate: + escalate: + escalate-auth: false + escalate-prompt: + configuration: + name: "configuration" + pattern: '(?im)^\S+@\S+:\S+:\S+#\s*$' + previous-priv: "exec" + deescalate: "exit" + escalate: "sudo su" + escalate-auth: true + escalate-prompt: ": " + default-desired-privilege-level: "exec" + failed-when-contains: + - "Permission denied" + - "ERROR:" + - "command not found" + textfsm-platform: "" + network-on-open: + - operation: "acquire-priv" + network-on-close: + - operation: "acquire-priv" + - operation: "channel.write" + input: "exit" + - operation: "channel.return" +variants: + root_login: + driver-type: "network" + privilege-levels: + exec: + name: "exec" + pattern: '(?im)^\S+@\S+:\S+:\S+#\s*$' + previous-priv: + deescalate: + escalate: + escalate-auth: false + escalate-prompt: ": " + configuration: + name: "configuration" + pattern: '(?im)^\S+@\S+:\S+:\S+#\s*$' + previous-priv: exec + deescalate: + escalate: + escalate-auth: false + escalate-prompt: + default-desired-privilege-level: "exec" + failed-when-contains: + - "Permission denied" + - "ERROR:" + textfsm-platform: "" + network-on-open: + - operation: "acquire-priv" + network-on-close: + - operation: "acquire-priv" + - operation: "channel.write" + input: "exit" + - operation: "channel.return" diff --git a/assets/platforms/cumulus_vtysh.yaml b/assets/platforms/cumulus_vtysh.yaml new file mode 100644 index 0000000..94e914e --- /dev/null +++ b/assets/platforms/cumulus_vtysh.yaml @@ -0,0 +1,43 @@ +--- +platform-type: 'cumulus_vtysh' +default: + driver-type: "network" + privilege-levels: + linux: + name: "linux" + pattern: '(?im)^\S+@\S+:\S+:\S+[\$|#]\s*$' + previous-priv: + deescalate: + escalate: + escalate-auth: false + escalate-prompt: + exec: + name: "exec" + pattern: '(?im)^[\w\.\-]+#\s*$' + previous-priv: "linux" + deescalate: "exit" + escalate: "vtysh" + escalate-auth: false + escalate-prompt: + configuration: + name: "configuration" + pattern: '(?im)^[\w\.\-]+\(config\)#\s*$' + previous-priv: "exec" + deescalate: "exit" + escalate: "configure terminal" + escalate-auth: false + escalate-prompt: + default-desired-privilege-level: "exec" + failed-when-contains: + - "Permission denied" + - "ERROR:" + - "% Unknown command" + - "% Command incomplete" + textfsm-platform: "" + network-on-open: + - operation: "acquire-priv" + network-on-close: + - operation: "acquire-priv" + - operation: "channel.write" + input: "exit" + - operation: "channel.return" diff --git a/driver/generic/driver_test.go b/driver/generic/driver_test.go index 7edfda3..d3241f7 100644 --- a/driver/generic/driver_test.go +++ b/driver/generic/driver_test.go @@ -82,6 +82,12 @@ func prepareDriver( options.WithFileTransportFile(resolveFile(t, payloadFile)), options.WithTransportReadSize(1), options.WithReadDelay(0), + options.WithFailedWhenContains([]string{ + "% Ambiguous command", + "% Incomplete command", + "% Invalid input detected", + "% Unknown command", + }), ) if err != nil { t.Errorf("%s: encountered error creating generic Driver, error: %s", testName, err) diff --git a/driver/generic/sendcommand_test.go b/driver/generic/sendcommand_test.go index 0ff93e4..a11ca08 100644 --- a/driver/generic/sendcommand_test.go +++ b/driver/generic/sendcommand_test.go @@ -83,3 +83,41 @@ func TestSendCommand(t *testing.T) { t.Run(testName, f) } } + +func testSendCommandFails(testName string, testCase *sendCommandTestCase) func(t *testing.T) { + return func(t *testing.T) { + t.Logf("%s: starting", testName) + + d, _ := prepareDriver(t, testName, testCase.payloadFile) + + r, err := d.SendCommand(testCase.command) + if err != nil { + t.Fatalf("%s: response object indicates failure", + testName) + } + + if r.Failed == nil { + t.Fatalf( + "%s: expected r.Failed to be set", + testName, + ) + } + } +} + +func TestSendCommandFails(t *testing.T) { + cases := map[string]*sendCommandTestCase{ + "send-command-failure-simple": { + description: "simple send command failure test", + command: "thiscommandshouldfail", + payloadFile: "send-command-failure-simple.txt", + stripPrompt: false, + eager: false, + }, + } + + for testName, testCase := range cases { + f := testSendCommandFails(testName, testCase) + t.Run(testName, f) + } +} diff --git a/driver/generic/test-fixtures/golden/send-commands-failure-simple-in.txt b/driver/generic/test-fixtures/golden/send-commands-failure-simple-in.txt new file mode 100644 index 0000000..d41b70a --- /dev/null +++ b/driver/generic/test-fixtures/golden/send-commands-failure-simple-in.txt @@ -0,0 +1,2 @@ +thiscommandshouldfail + diff --git a/driver/generic/test-fixtures/golden/send-commands-failure-simple-out.txt b/driver/generic/test-fixtures/golden/send-commands-failure-simple-out.txt new file mode 100644 index 0000000..8ec4e0f --- /dev/null +++ b/driver/generic/test-fixtures/golden/send-commands-failure-simple-out.txt @@ -0,0 +1 @@ +% Unknown command or computer name, or unable to find computer address diff --git a/driver/generic/test-fixtures/send-command-failure-simple.txt b/driver/generic/test-fixtures/send-command-failure-simple.txt new file mode 100644 index 0000000..101c328 --- /dev/null +++ b/driver/generic/test-fixtures/send-command-failure-simple.txt @@ -0,0 +1,3 @@ +C3560CX#thiscommandshouldfail +% Unknown command or computer name, or unable to find computer address +C3560CX# \ No newline at end of file diff --git a/examples/generic_driver/basics/main.go b/examples/generic_driver/basics/main.go index b860951..2d0ddcd 100644 --- a/examples/generic_driver/basics/main.go +++ b/examples/generic_driver/basics/main.go @@ -66,6 +66,7 @@ func main() { // note that there is a convenience wrapper around send interactive in the generic driver as // well, so you could simply do `d.SendInteractive` here rather than poking the channel directly + // remember to refer to `.Result` object in that case interactiveOutput, err := d.Channel.SendInteractive(events) if err != nil { fmt.Printf("failed to send interactive input to device; error: %+v\n", err) @@ -84,6 +85,10 @@ func main() { fmt.Printf("failed to send command; error: %+v\n", err) return } + if r.Failed != nil { + fmt.Printf("response objects indicates failure: %+v\n", r.Failed) + return + } fmt.Printf( "sent command '%s', output received (SendCommand):\n %s\n\n\n", diff --git a/examples/generic_driver/custom_logging/main.go b/examples/generic_driver/custom_logging/main.go index 3a4146d..88731d9 100644 --- a/examples/generic_driver/custom_logging/main.go +++ b/examples/generic_driver/custom_logging/main.go @@ -61,6 +61,11 @@ func main() { return } + if r.Failed != nil { + fmt.Printf("response object indicates failure: %+v\n", r.Failed) + + return + } fmt.Printf("got some output: %s\n\n\n", r.Result) } diff --git a/examples/generic_driver/default_logging/main.go b/examples/generic_driver/default_logging/main.go index 3884b32..fc48d14 100644 --- a/examples/generic_driver/default_logging/main.go +++ b/examples/generic_driver/default_logging/main.go @@ -37,6 +37,11 @@ func main() { return } + if r.Failed != nil { + fmt.Printf("response object indicates failure: %+v\n", r.Failed) + + return + } fmt.Printf("got some output: %s\n\n\n", r.Result) } diff --git a/examples/generic_driver/interactive_prompts/main.go b/examples/generic_driver/interactive_prompts/main.go index 82ec9b9..6b0fb1e 100644 --- a/examples/generic_driver/interactive_prompts/main.go +++ b/examples/generic_driver/interactive_prompts/main.go @@ -50,6 +50,11 @@ func main() { return } + if r.Failed != nil { + fmt.Printf("response object indicates failure: %+v\n", r.Failed) + + return + } fmt.Printf("interact response:\n%s\n", r.Result) } diff --git a/examples/generic_driver/textfsm_integration/main.go b/examples/generic_driver/textfsm_integration/main.go index 2661ee8..05e1ee1 100644 --- a/examples/generic_driver/textfsm_integration/main.go +++ b/examples/generic_driver/textfsm_integration/main.go @@ -44,6 +44,11 @@ func main() { return } + if r.Failed != nil { + fmt.Printf("response object indicates failure: %+v\n", r.Failed) + + return + } parsedOut, err := r.TextFsmParse(*arg) if err != nil { diff --git a/examples/netconf_driver/basics/main.go b/examples/netconf_driver/basics/main.go index d258422..1839dab 100644 --- a/examples/netconf_driver/basics/main.go +++ b/examples/netconf_driver/basics/main.go @@ -36,6 +36,11 @@ func main() { return } + if r.Failed != nil { + fmt.Printf("response object indicates failure: %+v\n", r.Failed) + + return + } fmt.Printf("Config result: %s", r.Result) } diff --git a/examples/network_driver/basics/main.go b/examples/network_driver/basics/main.go index 950385d..a62ffdf 100644 --- a/examples/network_driver/basics/main.go +++ b/examples/network_driver/basics/main.go @@ -103,6 +103,11 @@ func main() { return } + if interactiveOutput.Failed != nil { + fmt.Printf("response object indicates failure: %+v\n", interactiveOutput.Failed) + + return + } fmt.Printf("output received (SendInteractive):\n %s\n\n\n", interactiveOutput.Result) @@ -116,6 +121,11 @@ func main() { return } + if r.Failed != nil { + fmt.Printf("response object indicates failure: %+v\n", r.Failed) + + return + } fmt.Printf( "sent command '%s', output received (SendCommand):\n %s\n\n\n", diff --git a/examples/network_driver/platforms/main.go b/examples/network_driver/platforms/main.go index 55638c5..f2c0272 100644 --- a/examples/network_driver/platforms/main.go +++ b/examples/network_driver/platforms/main.go @@ -83,6 +83,11 @@ func main() { if err != nil { fmt.Printf("failed to send interactive input to device; error: %+v\n", err) } + if interactiveOutput.Failed != nil { + fmt.Printf("response object indicates failure: %+v\n", interactiveOutput.Failed) + + return + } fmt.Printf("output received (SendInteractive):\n %s\n\n\n", interactiveOutput.Result) @@ -94,6 +99,11 @@ func main() { fmt.Printf("failed to send command; error: %+v\n", err) return } + if r.Failed != nil { + fmt.Printf("response object indicates failure: %+v\n", r.Failed) + + return + } fmt.Printf( "sent command '%s', output received (SendCommand):\n %s\n\n\n", diff --git a/examples/network_driver/privilege_levels/main.go b/examples/network_driver/privilege_levels/main.go index 686f759..a774ee8 100644 --- a/examples/network_driver/privilege_levels/main.go +++ b/examples/network_driver/privilege_levels/main.go @@ -72,6 +72,11 @@ func main() { return } + if r.Failed != nil { + fmt.Printf("response object indicates failure: %+v\n", r.Failed) + + return + } fmt.Printf("got running config: %s\n\n\n", r.Result) } diff --git a/examples/network_driver/sending_configs/main.go b/examples/network_driver/sending_configs/main.go index 2d36ad9..e1f246c 100644 --- a/examples/network_driver/sending_configs/main.go +++ b/examples/network_driver/sending_configs/main.go @@ -48,6 +48,11 @@ func main() { return } + if r.Failed != nil { + fmt.Printf("response object indicates failure: %+v\n", r.Failed) + + return + } fmt.Printf("sending configs took %f seconds", r.ElapsedTime) }