Clarify run-if skip reasons with the resolved variable value.
Failed conditions now explain skips in plain language (e.g. UpdateCount was 0) and truncate long captures so misconfigured apt listings cannot flood the UI.
This commit is contained in:
@@ -108,12 +108,13 @@ func (executor *Executor) tickSchedules() {
|
||||
|
||||
// ResolvedAction is the effective action definition after library resolution.
|
||||
type ResolvedAction struct {
|
||||
Name string
|
||||
Description string
|
||||
Kind settings.ActionKind
|
||||
Body string
|
||||
Env []settings.ActionEnvVar
|
||||
Source settings.ActionItemSource
|
||||
Name string
|
||||
Description string
|
||||
Kind settings.ActionKind
|
||||
Body string
|
||||
Env []settings.ActionEnvVar
|
||||
RequiresSudo bool
|
||||
Source settings.ActionItemSource
|
||||
}
|
||||
|
||||
// ResolveItem resolves a library or local item to an executable action.
|
||||
@@ -130,12 +131,13 @@ func ResolveItem(item settings.NodeActionItem, library []settings.Action) (Resol
|
||||
env = []settings.ActionEnvVar{}
|
||||
}
|
||||
return ResolvedAction{
|
||||
Name: action.Name,
|
||||
Description: action.Description,
|
||||
Kind: action.Kind,
|
||||
Body: action.Body,
|
||||
Env: env,
|
||||
Source: settings.ActionItemSourceLibrary,
|
||||
Name: action.Name,
|
||||
Description: action.Description,
|
||||
Kind: action.Kind,
|
||||
Body: action.Body,
|
||||
Env: env,
|
||||
RequiresSudo: action.RequiresSudo,
|
||||
Source: settings.ActionItemSourceLibrary,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -155,18 +157,54 @@ func ResolveItem(item settings.NodeActionItem, library []settings.Action) (Resol
|
||||
env = []settings.ActionEnvVar{}
|
||||
}
|
||||
return ResolvedAction{
|
||||
Name: item.Name,
|
||||
Description: item.Description,
|
||||
Kind: item.Kind,
|
||||
Body: item.Body,
|
||||
Env: env,
|
||||
Source: settings.ActionItemSourceLocal,
|
||||
Name: item.Name,
|
||||
Description: item.Description,
|
||||
Kind: item.Kind,
|
||||
Body: item.Body,
|
||||
Env: env,
|
||||
RequiresSudo: item.RequiresSudo,
|
||||
Source: settings.ActionItemSourceLocal,
|
||||
}, nil
|
||||
default:
|
||||
return ResolvedAction{}, fmt.Errorf("unknown action item source %q", item.Source)
|
||||
}
|
||||
}
|
||||
|
||||
// WrapShellWithSudo prepends "sudo -n " when requiresSudo is true.
|
||||
func WrapShellWithSudo(command string, requiresSudo bool) string {
|
||||
if !requiresSudo {
|
||||
return command
|
||||
}
|
||||
return "sudo -n " + command
|
||||
}
|
||||
|
||||
// ScriptRemoteCommand is the remote argv used for script actions (body on stdin).
|
||||
func ScriptRemoteCommand(requiresSudo bool) string {
|
||||
if requiresSudo {
|
||||
return "sudo -n bash -s"
|
||||
}
|
||||
return "bash -s"
|
||||
}
|
||||
|
||||
// BuildRemoteCommand returns the final SSH exec string for an action (including env exports).
|
||||
func BuildRemoteCommand(
|
||||
kind settings.ActionKind,
|
||||
body string,
|
||||
requiresSudo bool,
|
||||
env map[string]string,
|
||||
) string {
|
||||
var command string
|
||||
switch kind {
|
||||
case settings.ActionKindShell:
|
||||
command = WrapShellWithSudo(body, requiresSudo)
|
||||
case settings.ActionKindScript:
|
||||
command = ScriptRemoteCommand(requiresSudo)
|
||||
default:
|
||||
command = body
|
||||
}
|
||||
return auth.PrependEnvExports(env, command)
|
||||
}
|
||||
|
||||
// RunGroup executes all items in order, appends a log file entry, and updates LastRunAt.
|
||||
func (executor *Executor) RunGroup(
|
||||
group settings.NodeActionGroup,
|
||||
@@ -220,6 +258,7 @@ func (executor *Executor) runItems(
|
||||
|
||||
startedAt := time.Now().UTC()
|
||||
results := make([]settings.ActionItemRunResult, 0, len(items))
|
||||
runVars := map[string]string{}
|
||||
|
||||
for _, item := range items {
|
||||
itemStarted := time.Now().UTC()
|
||||
@@ -239,9 +278,47 @@ func (executor *Executor) runItems(
|
||||
continue
|
||||
}
|
||||
|
||||
runIf := strings.TrimSpace(item.RunIf)
|
||||
if runIf != "" {
|
||||
shouldRun, detail, condErr := EvaluateCondition(runIf, runVars)
|
||||
if condErr != nil || !shouldRun {
|
||||
skipReason := fmt.Sprintf("%s skipped: run condition was not met", resolved.Name)
|
||||
if detail != "" {
|
||||
skipReason = fmt.Sprintf("%s skipped: %s", resolved.Name, detail)
|
||||
}
|
||||
if condErr != nil {
|
||||
skipReason = fmt.Sprintf("%s skipped because condition error: %s", resolved.Name, condErr.Error())
|
||||
}
|
||||
results = append(results, settings.ActionItemRunResult{
|
||||
ItemID: item.ID,
|
||||
ActionName: resolved.Name,
|
||||
Source: string(resolved.Source),
|
||||
ExitCode: 0,
|
||||
Stdout: "",
|
||||
Stderr: "",
|
||||
Skipped: true,
|
||||
SkipReason: skipReason,
|
||||
StartedAt: itemStarted,
|
||||
FinishedAt: time.Now().UTC(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
rawEnv := make(map[string]string, len(resolved.Env))
|
||||
for _, envVar := range resolved.Env {
|
||||
rawEnv[envVar.Name] = envVar.Value
|
||||
}
|
||||
envMap := make(map[string]string, len(resolved.Env))
|
||||
for _, envVar := range resolved.Env {
|
||||
envMap[envVar.Name] = envVar.Value
|
||||
expandedValue := auth.ExpandPlaceholders(envVar.Value, auth.PlaceholderContext{
|
||||
Host: node.Name,
|
||||
IP: node.HostIP,
|
||||
Username: node.Username,
|
||||
Env: rawEnv,
|
||||
Secrets: map[string]string{},
|
||||
})
|
||||
envMap[envVar.Name] = ExpandDollarVars(expandedValue, runVars)
|
||||
}
|
||||
|
||||
expandedBody := auth.ExpandPlaceholders(resolved.Body, auth.PlaceholderContext{
|
||||
@@ -251,6 +328,14 @@ func (executor *Executor) runItems(
|
||||
Env: envMap,
|
||||
Secrets: map[string]string{},
|
||||
})
|
||||
expandedBody = ExpandDollarVars(expandedBody, runVars)
|
||||
|
||||
remoteCommand := BuildRemoteCommand(
|
||||
resolved.Kind,
|
||||
expandedBody,
|
||||
resolved.RequiresSudo,
|
||||
envMap,
|
||||
)
|
||||
|
||||
var sshResult auth.SSHRunResult
|
||||
var runErr error
|
||||
@@ -261,7 +346,7 @@ func (executor *Executor) runItems(
|
||||
node.Username,
|
||||
privateKey,
|
||||
passphrase,
|
||||
expandedBody,
|
||||
WrapShellWithSudo(expandedBody, resolved.RequiresSudo),
|
||||
envMap,
|
||||
defaultRunTimeout,
|
||||
)
|
||||
@@ -274,26 +359,35 @@ func (executor *Executor) runItems(
|
||||
expandedBody,
|
||||
envMap,
|
||||
defaultRunTimeout,
|
||||
resolved.RequiresSudo,
|
||||
)
|
||||
default:
|
||||
runErr = fmt.Errorf("unsupported action kind %q", resolved.Kind)
|
||||
}
|
||||
|
||||
itemResult := settings.ActionItemRunResult{
|
||||
ItemID: item.ID,
|
||||
ActionName: resolved.Name,
|
||||
Source: string(resolved.Source),
|
||||
ExitCode: sshResult.ExitCode,
|
||||
Stdout: sshResult.Stdout,
|
||||
Stderr: sshResult.Stderr,
|
||||
StartedAt: itemStarted,
|
||||
FinishedAt: time.Now().UTC(),
|
||||
ItemID: item.ID,
|
||||
ActionName: resolved.Name,
|
||||
Source: string(resolved.Source),
|
||||
Kind: resolved.Kind,
|
||||
RemoteCommand: remoteCommand,
|
||||
SSHUsername: node.Username,
|
||||
ExitCode: sshResult.ExitCode,
|
||||
Stdout: sshResult.Stdout,
|
||||
Stderr: sshResult.Stderr,
|
||||
StartedAt: itemStarted,
|
||||
FinishedAt: time.Now().UTC(),
|
||||
}
|
||||
if runErr != nil {
|
||||
itemResult.Error = runErr.Error()
|
||||
if itemResult.ExitCode == 0 {
|
||||
itemResult.ExitCode = -1
|
||||
}
|
||||
} else if setName := strings.TrimSpace(item.SetVariable); setName != "" {
|
||||
captured := strings.TrimSpace(sshResult.Stdout)
|
||||
runVars[setName] = captured
|
||||
itemResult.SetVariable = setName
|
||||
itemResult.VariableValue = captured
|
||||
}
|
||||
results = append(results, itemResult)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user