mirror of
https://github.com/TwiN/gatus.git
synced 2026-02-04 12:56:48 +00:00
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 ae93e38683.
* 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 <twin@linux.com>
This commit is contained in:
@@ -3031,12 +3031,13 @@ endpoints:
|
|||||||
password: "password"
|
password: "password"
|
||||||
body: |
|
body: |
|
||||||
{
|
{
|
||||||
"command": "uptime"
|
"command": "echo '{\"memory\": {\"used\": 512}}'"
|
||||||
}
|
}
|
||||||
interval: 1m
|
interval: 1m
|
||||||
conditions:
|
conditions:
|
||||||
- "[CONNECTED] == true"
|
- "[CONNECTED] == true"
|
||||||
- "[STATUS] == 0"
|
- "[STATUS] == 0"
|
||||||
|
- "[BODY].memory.used > 500"
|
||||||
```
|
```
|
||||||
|
|
||||||
you can also use no authentication to monitor the endpoint by not specifying the username
|
you can also use no authentication to monitor the endpoint by not specifying the username
|
||||||
@@ -3059,6 +3060,7 @@ endpoints:
|
|||||||
The following placeholders are supported for endpoints of type SSH:
|
The following placeholders are supported for endpoints of type SSH:
|
||||||
- `[CONNECTED]` resolves to `true` if the SSH connection was successful, `false` otherwise
|
- `[CONNECTED]` resolves to `true` if the SSH connection was successful, `false` otherwise
|
||||||
- `[STATUS]` resolves the exit code of the command executed on the remote server (e.g. `0` for success)
|
- `[STATUS]` resolves the exit code of the command executed on the remote server (e.g. `0` for success)
|
||||||
|
- `[BODY]` resolves to the stdout output of the command executed on the remote server
|
||||||
- `[IP]` resolves to the IP address of the server
|
- `[IP]` resolves to the IP address of the server
|
||||||
- `[RESPONSE_TIME]` resolves to the time it took to establish the connection and execute the command
|
- `[RESPONSE_TIME]` resolves to the time it took to establish the connection and execute the command
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
@@ -301,7 +302,7 @@ func CheckSSHBanner(address string, cfg *Config) (bool, int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ExecuteSSHCommand executes a command to an address using the SSH protocol.
|
// ExecuteSSHCommand executes a command to an address using the SSH protocol.
|
||||||
func ExecuteSSHCommand(sshClient *ssh.Client, body string, config *Config) (bool, int, error) {
|
func ExecuteSSHCommand(sshClient *ssh.Client, body string, config *Config) (bool, int, []byte, error) {
|
||||||
type Body struct {
|
type Body struct {
|
||||||
Command string `json:"command"`
|
Command string `json:"command"`
|
||||||
}
|
}
|
||||||
@@ -309,26 +310,30 @@ func ExecuteSSHCommand(sshClient *ssh.Client, body string, config *Config) (bool
|
|||||||
var b Body
|
var b Body
|
||||||
body = parseLocalAddressPlaceholder(body, sshClient.Conn.LocalAddr())
|
body = parseLocalAddressPlaceholder(body, sshClient.Conn.LocalAddr())
|
||||||
if err := json.Unmarshal([]byte(body), &b); err != nil {
|
if err := json.Unmarshal([]byte(body), &b); err != nil {
|
||||||
return false, 0, err
|
return false, 0, nil, err
|
||||||
}
|
}
|
||||||
sess, err := sshClient.NewSession()
|
sess, err := sshClient.NewSession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, 0, err
|
return false, 0, nil, err
|
||||||
}
|
}
|
||||||
|
// Capture stdout
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
sess.Stdout = &stdout
|
||||||
err = sess.Start(b.Command)
|
err = sess.Start(b.Command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, 0, err
|
return false, 0, nil, err
|
||||||
}
|
}
|
||||||
defer sess.Close()
|
defer sess.Close()
|
||||||
err = sess.Wait()
|
err = sess.Wait()
|
||||||
|
output := stdout.Bytes()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return true, 0, nil
|
return true, 0, output, nil
|
||||||
}
|
}
|
||||||
var exitErr *ssh.ExitError
|
var exitErr *ssh.ExitError
|
||||||
if ok := errors.As(err, &exitErr); !ok {
|
if ok := errors.As(err, &exitErr); !ok {
|
||||||
return false, 0, err
|
return false, 0, nil, err
|
||||||
}
|
}
|
||||||
return true, exitErr.ExitStatus(), nil
|
return true, exitErr.ExitStatus(), output, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ping checks if an address can be pinged and returns the round-trip time if the address can be pinged
|
// Ping checks if an address can be pinged and returns the round-trip time if the address can be pinged
|
||||||
|
|||||||
@@ -514,11 +514,16 @@ func (e *Endpoint) call(result *Result) {
|
|||||||
result.AddError(err.Error())
|
result.AddError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
result.Success, result.HTTPStatus, err = client.ExecuteSSHCommand(cli, e.getParsedBody(), e.ClientConfig)
|
var output []byte
|
||||||
|
result.Success, result.HTTPStatus, output, err = client.ExecuteSSHCommand(cli, e.getParsedBody(), e.ClientConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.AddError(err.Error())
|
result.AddError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Only store the output in result.Body if there's a condition that uses the BodyPlaceholder
|
||||||
|
if e.needsToReadBody() {
|
||||||
|
result.Body = output
|
||||||
|
}
|
||||||
result.Duration = time.Since(startTime)
|
result.Duration = time.Since(startTime)
|
||||||
} else {
|
} else {
|
||||||
response, err = client.GetHTTPClient(e.ClientConfig).Do(request)
|
response, err = client.GetHTTPClient(e.ClientConfig).Do(request)
|
||||||
|
|||||||
Reference in New Issue
Block a user