diff --git a/executor.go b/executor.go index 5150bc7..34227d0 100644 --- a/executor.go +++ b/executor.go @@ -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) diff --git a/executor_test.go b/executor_test.go index aa63988..c3a4faa 100644 --- a/executor_test.go +++ b/executor_test.go @@ -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()