feat(ansible): expose full extended loop metadata

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 22:53:34 +00:00
parent defcd18f44
commit b67d9419a4
2 changed files with 42 additions and 0 deletions

View file

@ -857,6 +857,14 @@ func (e *Executor) runLoop(ctx context.Context, host string, client sshExecutorC
e.vars[indexVar] = i
}
if task.LoopControl != nil && task.LoopControl.Extended {
var prevItem any
if i > 0 {
prevItem = items[i-1]
}
var nextItem any
if i+1 < len(items) {
nextItem = items[i+1]
}
loopMeta := map[string]any{
"index": i + 1,
"index0": i,
@ -865,6 +873,13 @@ func (e *Executor) runLoop(ctx context.Context, host string, client sshExecutorC
"length": len(items),
"revindex": len(items) - i,
"revindex0": len(items) - i - 1,
"allitems": append([]any(nil), items...),
}
if prevItem != nil {
loopMeta["previtem"] = prevItem
}
if nextItem != nil {
loopMeta["nextitem"] = nextItem
}
if task.LoopControl.Label != "" {
loopMeta["label"] = e.templateString(task.LoopControl.Label, host, task)

View file

@ -655,6 +655,33 @@ func TestExecutor_RunTaskOnHost_Good_LoopControlExtendedExposesMetadata(t *testi
assert.Equal(t, "two 1/2 first=false last=true", result.Results[1].Msg)
}
func TestExecutor_RunTaskOnHost_Good_LoopControlExtendedExposesNeighbourItems(t *testing.T) {
e := NewExecutor("/tmp")
e.clients["host1"] = NewMockSSHClient()
task := &Task{
Name: "Neighbour loop metadata",
Module: "debug",
Args: map[string]any{
"msg": "prev={{ ansible_loop.previtem | default('NONE') }} next={{ ansible_loop.nextitem | default('NONE') }} all={{ ansible_loop.allitems }}",
},
Loop: []any{"one", "two"},
LoopControl: &LoopControl{
Extended: true,
},
Register: "loop_result",
}
err := e.runTaskOnHosts(context.Background(), []string{"host1"}, task, &Play{})
require.NoError(t, err)
result := e.results["host1"]["loop_result"]
require.NotNil(t, result)
require.Len(t, result.Results, 2)
assert.Equal(t, "prev=NONE next=two all=[one two]", result.Results[0].Msg)
assert.Equal(t, "prev=one next=NONE all=[one two]", result.Results[1].Msg)
}
func TestExecutor_RunTaskOnHost_Good_LoopFromWithDictItems(t *testing.T) {
e := NewExecutor("/tmp")
e.clients["host1"] = NewMockSSHClient()