Add fleetctl run-script command (#13622)
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/service"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func runScriptCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "run-script",
|
||||
Aliases: []string{"run_script"},
|
||||
Usage: `Run a script on one host.`,
|
||||
UsageText: `fleetctl run-script [options]`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "script-path",
|
||||
Usage: "The path to the script.",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "host",
|
||||
Usage: "A host, specified by hostname, UUID, osquery host ID, or node key.",
|
||||
Required: true,
|
||||
},
|
||||
configFlag(),
|
||||
contextFlag(),
|
||||
debugFlag(),
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
client, err := clientFromCLI(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path := c.String("script-path")
|
||||
if err := validateScriptPath(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ident := c.String("host")
|
||||
h, err := client.HostByIdentifier(ident)
|
||||
if err != nil {
|
||||
var nfe service.NotFoundErr
|
||||
if errors.As(err, &nfe) {
|
||||
return errors.New(fleet.RunScriptHostNotFoundErrMsg)
|
||||
}
|
||||
var sce fleet.ErrWithStatusCode
|
||||
if errors.As(err, &sce) {
|
||||
if sce.StatusCode() == http.StatusForbidden {
|
||||
return errors.New(fleet.RunScriptForbiddenErrMsg)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if h.Status != fleet.StatusOnline {
|
||||
return errors.New(fleet.RunScriptHostOfflineErrMsg)
|
||||
}
|
||||
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fleet.ValidateHostScriptContents(string(b)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := client.RunHostScriptSync(h.ID, b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := renderScriptResult(c, res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func renderScriptResult(c *cli.Context, res *fleet.HostScriptResult) error {
|
||||
tmpl := template.Must(template.New("").Parse(`
|
||||
{{ if .ErrorMsg -}}
|
||||
Error: {{ .ErrorMsg }}
|
||||
{{- else -}}
|
||||
Exit code: {{ .ExitCode }} ({{ .ExitMessage }})
|
||||
{{- end }}
|
||||
{{ if .ShowOutput }}
|
||||
Output {{- if .ExecTimeout }} before timeout {{- end }}:
|
||||
|
||||
-------------------------------------------------------------------------------------
|
||||
|
||||
{{ .Output }}
|
||||
|
||||
-------------------------------------------------------------------------------------
|
||||
{{- end }}
|
||||
`))
|
||||
|
||||
data := struct {
|
||||
ExecTimeout bool
|
||||
ErrorMsg string
|
||||
ExitCode *int64
|
||||
ExitMessage string
|
||||
Output string
|
||||
ShowOutput bool
|
||||
}{
|
||||
ExitCode: res.ExitCode,
|
||||
ExitMessage: "Script failed.",
|
||||
ShowOutput: true,
|
||||
}
|
||||
|
||||
switch {
|
||||
case res.ExitCode == nil:
|
||||
data.ErrorMsg = res.Message
|
||||
case *res.ExitCode == -2:
|
||||
data.ShowOutput = false
|
||||
data.ErrorMsg = res.Message
|
||||
case *res.ExitCode == -1:
|
||||
data.ExecTimeout = true
|
||||
data.ErrorMsg = res.Message
|
||||
case *res.ExitCode == 0:
|
||||
data.ExitMessage = "Script ran successfully."
|
||||
}
|
||||
|
||||
if len(res.Output) >= fleet.MaxScriptRuneLen && utf8.RuneCountInString(res.Output) >= fleet.MaxScriptRuneLen {
|
||||
data.Output = "Fleet records the last 10,000 characters to prevent downtime.\n\n" + res.Output
|
||||
} else {
|
||||
data.Output = res.Output
|
||||
}
|
||||
|
||||
return tmpl.Execute(c.App.Writer, data)
|
||||
}
|
||||
|
||||
func validateScriptPath(path string) error {
|
||||
extension := filepath.Ext(path)
|
||||
if extension == ".sh" || extension == ".ps1" {
|
||||
return nil
|
||||
}
|
||||
return errors.New(fleet.RunScriptInvalidTypeErrMsg)
|
||||
}
|
||||
Reference in New Issue
Block a user