chore: publish pxmon v0.2.0
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"pxmon/internal/cluster"
|
||||
)
|
||||
|
||||
type observerCommandOptions struct {
|
||||
AllowShellEscape bool
|
||||
StatsAutoOnce bool
|
||||
BlockBotRun bool
|
||||
StripANSI bool
|
||||
EmbeddedConsole bool
|
||||
}
|
||||
|
||||
var ansiEscapeRE = regexp.MustCompile(`\x1b\[[0-9;?]*[ -/]*[@-~]`)
|
||||
|
||||
func stripANSI(s string) string {
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
return ansiEscapeRE.ReplaceAllString(s, "")
|
||||
}
|
||||
|
||||
func runObserverScopedCommand(svc *cluster.Service, configPath, line string, opts observerCommandOptions) (string, int) {
|
||||
out, code := runObserverScopedCommandInner(svc, configPath, line, opts)
|
||||
if opts.StripANSI {
|
||||
out = stripANSI(out)
|
||||
}
|
||||
return out, code
|
||||
}
|
||||
|
||||
func runObserverScopedCommandInner(svc *cluster.Service, configPath, line string, opts observerCommandOptions) (string, int) {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
return "empty command", 2
|
||||
}
|
||||
|
||||
if strings.HasPrefix(line, "!") {
|
||||
if !opts.AllowShellEscape {
|
||||
return "shell escape is disabled for this channel", 2
|
||||
}
|
||||
cmdline := strings.TrimSpace(strings.TrimPrefix(line, "!"))
|
||||
if cmdline == "" {
|
||||
return "usage: !<shell command>", 2
|
||||
}
|
||||
cmd := exec.Command("/bin/sh", "-lc", cmdline)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
return string(out), exitErr.ExitCode()
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return err.Error(), 1
|
||||
}
|
||||
return string(out), 1
|
||||
}
|
||||
return string(out), 0
|
||||
}
|
||||
|
||||
args, err := parseShellArgs(line)
|
||||
if err != nil || len(args) == 0 {
|
||||
if err != nil {
|
||||
return err.Error(), 2
|
||||
}
|
||||
return "empty command", 2
|
||||
}
|
||||
|
||||
norm := normalizeObserverConsoleArgs(args)
|
||||
if len(norm) == 0 {
|
||||
return "empty command", 2
|
||||
}
|
||||
|
||||
if opts.BlockBotRun && len(norm) >= 3 &&
|
||||
strings.EqualFold(norm[0], "bot") &&
|
||||
strings.EqualFold(norm[1], "telegram") &&
|
||||
strings.EqualFold(norm[2], "run") {
|
||||
return "running `bot telegram run` from bot channel is blocked", 2
|
||||
}
|
||||
|
||||
if opts.StatsAutoOnce && len(norm) >= 2 &&
|
||||
strings.EqualFold(norm[0], "cluster") &&
|
||||
strings.EqualFold(norm[1], "stats") &&
|
||||
!hasArg(norm[2:], "--once") {
|
||||
norm = append(norm, "--once")
|
||||
}
|
||||
|
||||
if handled, out, code := runPluginArgs(svc, norm); handled {
|
||||
return out, code
|
||||
}
|
||||
|
||||
return runObserverCommand(configPath, norm, opts.EmbeddedConsole)
|
||||
}
|
||||
|
||||
func runPluginArgs(svc *cluster.Service, args []string) (bool, string, int) {
|
||||
if len(args) == 0 || svc == nil {
|
||||
return false, "", 0
|
||||
}
|
||||
|
||||
tool := strings.ToLower(strings.TrimSpace(args[0]))
|
||||
switch tool {
|
||||
case "kvm", "lxc", "lxd", "bird", "frr":
|
||||
default:
|
||||
return false, "", 0
|
||||
}
|
||||
|
||||
selector, rest, err := parseClusterSelectorArg(args[1:])
|
||||
if err != nil {
|
||||
return true, err.Error(), 2
|
||||
}
|
||||
|
||||
action := ""
|
||||
params := []string{}
|
||||
if len(rest) > 0 {
|
||||
action = strings.ToLower(strings.TrimSpace(rest[0]))
|
||||
params = rest[1:]
|
||||
}
|
||||
if action == "" {
|
||||
switch tool {
|
||||
case "kvm", "lxc", "lxd":
|
||||
action = "list"
|
||||
default:
|
||||
action = "status"
|
||||
}
|
||||
}
|
||||
if tool == "kvm" && action == "top" {
|
||||
for _, p := range params {
|
||||
if strings.EqualFold(strings.TrimSpace(p), "--live") || strings.EqualFold(strings.TrimSpace(p), "-L") {
|
||||
return true, "kvm top --live is removed; use `kvm top` for allocated VM specs", 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), pluginActionTimeout(tool, action, false))
|
||||
defer cancel()
|
||||
|
||||
out, execErr := svc.RunPluginAction(ctx, selector, tool, action, params)
|
||||
if execErr != nil {
|
||||
return true, execErr.Error(), 1
|
||||
}
|
||||
if strings.TrimSpace(out) == "" {
|
||||
out = "ok"
|
||||
}
|
||||
return true, out, 0
|
||||
}
|
||||
|
||||
func pluginActionTimeout(tool, action string, live bool) time.Duration {
|
||||
tool = strings.ToLower(strings.TrimSpace(tool))
|
||||
action = strings.ToLower(strings.TrimSpace(action))
|
||||
switch tool {
|
||||
case "kvm":
|
||||
switch action {
|
||||
case "top":
|
||||
if live {
|
||||
return 2 * time.Minute
|
||||
}
|
||||
return 90 * time.Second
|
||||
case "net-top", "net":
|
||||
if live {
|
||||
return 90 * time.Second
|
||||
}
|
||||
return 60 * time.Second
|
||||
default:
|
||||
return 45 * time.Second
|
||||
}
|
||||
case "lxc", "lxd":
|
||||
if action == "top" || action == "net-top" || action == "net" {
|
||||
return 45 * time.Second
|
||||
}
|
||||
}
|
||||
if live {
|
||||
return 35 * time.Second
|
||||
}
|
||||
return 25 * time.Second
|
||||
}
|
||||
Reference in New Issue
Block a user