Fix loop task finalisation

This commit is contained in:
Virgil 2026-04-01 23:34:33 +00:00
parent 0e3a362269
commit 2655775a8f
2 changed files with 90 additions and 2 deletions

View file

@ -663,7 +663,7 @@ func (e *Executor) runTaskOnHost(ctx context.Context, host string, hosts []strin
// Handle loops, including legacy with_file, with_fileglob, and with_sequence syntax.
if task.Loop != nil || task.WithFile != nil || task.WithFileGlob != nil || task.WithSequence != nil {
return e.runLoop(ctx, host, client, task, play)
return e.runLoop(ctx, host, client, task, play, start)
}
// Execute the task, honouring retries/until when configured.
@ -853,7 +853,7 @@ func shouldRetryTask(task *Task, host string, e *Executor, result *TaskResult) b
}
// runLoop handles task loops.
func (e *Executor) runLoop(ctx context.Context, host string, client sshExecutorClient, task *Task, play *Play) error {
func (e *Executor) runLoop(ctx context.Context, host string, client sshExecutorClient, task *Task, play *Play, start time.Time) error {
var (
items []any
err error
@ -991,9 +991,44 @@ func (e *Executor) runLoop(ctx context.Context, host string, client sshExecutorC
combined.Failed = true
}
}
combined.Duration = time.Since(start)
e.results[host][task.Register] = combined
}
result := &TaskResult{
Results: results,
Changed: false,
}
for _, r := range results {
if r.Changed {
result.Changed = true
}
if r.Failed {
result.Failed = true
}
}
result.Duration = time.Since(start)
displayResult := result
if task.NoLog {
displayResult = redactTaskResult(result)
}
if result.Changed && task.Notify != nil {
e.handleNotify(task.Notify)
}
if e.OnTaskEnd != nil {
e.OnTaskEnd(host, task, displayResult)
}
if result.Failed {
e.markBatchHostFailed(host)
if !task.IgnoreErrors {
return taskFailureError(task, result)
}
}
return nil
}

View file

@ -919,6 +919,59 @@ func TestExecutor_RunTaskOnHost_Good_LoopFromWithNested(t *testing.T) {
assert.Equal(t, "blue-large", result.Results[3].Msg)
}
func TestExecutor_RunTaskOnHosts_Good_LoopNotifiesAndCallsCallback(t *testing.T) {
e := NewExecutor("/tmp")
e.clients["host1"] = NewMockSSHClient()
var ended *TaskResult
task := &Task{
Name: "Looped change",
Module: "set_fact",
Args: map[string]any{
"changed_flag": true,
},
Loop: []any{"one", "two"},
Notify: "restart app",
}
play := &Play{
Handlers: []Task{
{
Name: "restart app",
Module: "debug",
Args: map[string]any{"msg": "handler"},
},
},
}
e.OnTaskEnd = func(_ string, _ *Task, result *TaskResult) {
ended = result
}
err := e.runTaskOnHosts(context.Background(), []string{"host1"}, task, play)
require.NoError(t, err)
require.NotNil(t, ended)
assert.True(t, ended.Changed)
assert.Len(t, ended.Results, 2)
assert.True(t, e.notified["restart app"])
}
func TestExecutor_RunTaskOnHosts_Bad_LoopFailurePropagates(t *testing.T) {
e := NewExecutor("/tmp")
e.clients["host1"] = NewMockSSHClient()
task := &Task{
Name: "Looped failure",
Module: "fail",
Args: map[string]any{"msg": "bad"},
Loop: []any{"one", "two"},
}
err := e.runTaskOnHosts(context.Background(), []string{"host1"}, task, &Play{})
require.Error(t, err)
assert.Contains(t, err.Error(), "task failed")
}
func TestExecutor_RunTaskWithRetries_Good_UntilSuccess(t *testing.T) {
e := NewExecutor("/tmp")
attempts := 0