Skip to content

Commit 5c78bd9

Browse files
yansh97TwiN
andauthored
feat(client): Support body placeholder for SSH endpoints (#1286)
* feat(ssh): Add BODY placeholder support for SSH endpoints - Modify ExecuteSSHCommand to capture stdout output - Update SSH endpoint handling to use needsToReadBody() mechanism - Add comprehensive test cases for SSH BODY functionality - Support basic body content, pattern matching, JSONPath, and functions - Maintain backward compatibility with existing SSH endpoints * docs: Add SSH BODY placeholder examples to README - Add [BODY] placeholder to supported SSH placeholders list - Add comprehensive examples showing various SSH BODY conditions - Include pattern matching, length checks, JSONPath expressions - Demonstrate function wrappers (len, has, any) usage * Revert "docs: Add SSH BODY placeholder examples to README" This reverts commit ae93e38. * docs: Add [BODY] placeholder to SSH supported placeholders list * test: remove SSH BODY placeholder test cases * Update client/client.go * Update client/client.go * docs: Add minimal SSH BODY example --------- Co-authored-by: TwiN <[email protected]>
1 parent 8853140 commit 5c78bd9

File tree

3 files changed

+21
-9
lines changed

3 files changed

+21
-9
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3031,12 +3031,13 @@ endpoints:
30313031
password: "password"
30323032
body: |
30333033
{
3034-
"command": "uptime"
3034+
"command": "echo '{\"memory\": {\"used\": 512}}'"
30353035
}
30363036
interval: 1m
30373037
conditions:
30383038
- "[CONNECTED] == true"
30393039
- "[STATUS] == 0"
3040+
- "[BODY].memory.used > 500"
30403041
```
30413042

30423043
you can also use no authentication to monitor the endpoint by not specifying the username
@@ -3059,6 +3060,7 @@ endpoints:
30593060
The following placeholders are supported for endpoints of type SSH:
30603061
- `[CONNECTED]` resolves to `true` if the SSH connection was successful, `false` otherwise
30613062
- `[STATUS]` resolves the exit code of the command executed on the remote server (e.g. `0` for success)
3063+
- `[BODY]` resolves to the stdout output of the command executed on the remote server
30623064
- `[IP]` resolves to the IP address of the server
30633065
- `[RESPONSE_TIME]` resolves to the time it took to establish the connection and execute the command
30643066

client/client.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package client
22

33
import (
4+
"bytes"
45
"context"
56
"crypto/tls"
67
"crypto/x509"
@@ -301,34 +302,38 @@ func CheckSSHBanner(address string, cfg *Config) (bool, int, error) {
301302
}
302303

303304
// ExecuteSSHCommand executes a command to an address using the SSH protocol.
304-
func ExecuteSSHCommand(sshClient *ssh.Client, body string, config *Config) (bool, int, error) {
305+
func ExecuteSSHCommand(sshClient *ssh.Client, body string, config *Config) (bool, int, []byte, error) {
305306
type Body struct {
306307
Command string `json:"command"`
307308
}
308309
defer sshClient.Close()
309310
var b Body
310311
body = parseLocalAddressPlaceholder(body, sshClient.Conn.LocalAddr())
311312
if err := json.Unmarshal([]byte(body), &b); err != nil {
312-
return false, 0, err
313+
return false, 0, nil, err
313314
}
314315
sess, err := sshClient.NewSession()
315316
if err != nil {
316-
return false, 0, err
317+
return false, 0, nil, err
317318
}
319+
// Capture stdout
320+
var stdout bytes.Buffer
321+
sess.Stdout = &stdout
318322
err = sess.Start(b.Command)
319323
if err != nil {
320-
return false, 0, err
324+
return false, 0, nil, err
321325
}
322326
defer sess.Close()
323327
err = sess.Wait()
328+
output := stdout.Bytes()
324329
if err == nil {
325-
return true, 0, nil
330+
return true, 0, output, nil
326331
}
327332
var exitErr *ssh.ExitError
328333
if ok := errors.As(err, &exitErr); !ok {
329-
return false, 0, err
334+
return false, 0, nil, err
330335
}
331-
return true, exitErr.ExitStatus(), nil
336+
return true, exitErr.ExitStatus(), output, nil
332337
}
333338

334339
// Ping checks if an address can be pinged and returns the round-trip time if the address can be pinged

config/endpoint/endpoint.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,11 +514,16 @@ func (e *Endpoint) call(result *Result) {
514514
result.AddError(err.Error())
515515
return
516516
}
517-
result.Success, result.HTTPStatus, err = client.ExecuteSSHCommand(cli, e.getParsedBody(), e.ClientConfig)
517+
var output []byte
518+
result.Success, result.HTTPStatus, output, err = client.ExecuteSSHCommand(cli, e.getParsedBody(), e.ClientConfig)
518519
if err != nil {
519520
result.AddError(err.Error())
520521
return
521522
}
523+
// Only store the output in result.Body if there's a condition that uses the BodyPlaceholder
524+
if e.needsToReadBody() {
525+
result.Body = output
526+
}
522527
result.Duration = time.Since(startTime)
523528
} else {
524529
response, err = client.GetHTTPClient(e.ClientConfig).Do(request)

0 commit comments

Comments
 (0)