feat(ansible): expose loop label without extended metadata

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 01:58:31 +00:00
parent f5c4f16d42
commit 35014b52fc
2 changed files with 54 additions and 26 deletions

View file

@ -911,7 +911,7 @@ func (e *Executor) runLoop(ctx context.Context, host string, client sshExecutorC
}
}
var savedLoopMeta any
if task.LoopControl != nil && task.LoopControl.Extended {
if task.LoopControl != nil && (task.LoopControl.Extended || task.LoopControl.Label != "") {
if v, ok := e.vars["ansible_loop"]; ok {
savedLoopMeta = v
}
@ -924,30 +924,31 @@ func (e *Executor) runLoop(ctx context.Context, host string, client sshExecutorC
if indexVar != "" {
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,
"first": i == 0,
"last": i == len(items)-1,
"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 != nil && (task.LoopControl.Extended || task.LoopControl.Label != "") {
loopMeta := map[string]any{}
if 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["index"] = i + 1
loopMeta["index0"] = i
loopMeta["first"] = i == 0
loopMeta["last"] = i == len(items)-1
loopMeta["length"] = len(items)
loopMeta["revindex"] = len(items) - i
loopMeta["revindex0"] = len(items) - i - 1
loopMeta["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)
@ -991,7 +992,7 @@ func (e *Executor) runLoop(ctx context.Context, host string, client sshExecutorC
delete(e.vars, indexVar)
}
}
if task.LoopControl != nil && task.LoopControl.Extended {
if task.LoopControl != nil && (task.LoopControl.Extended || task.LoopControl.Label != "") {
if savedLoopMeta != nil {
e.vars["ansible_loop"] = savedLoopMeta
} else {

View file

@ -840,6 +840,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_LoopControlLabelWithoutExtended(t *testing.T) {
e := NewExecutor("/tmp")
e.clients["host1"] = NewMockSSHClient()
task := &Task{
Name: "Label-only loop metadata",
Module: "debug",
Args: map[string]any{
"msg": "{{ ansible_loop.label }}={{ item }}",
},
Loop: []any{"one", "two"},
LoopControl: &LoopControl{
Label: "{{ item }}",
},
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, "one=one", result.Results[0].Msg)
assert.Equal(t, "two=two", result.Results[1].Msg)
}
func TestExecutor_RunTaskOnHost_Good_LoopControlExtendedExposesNeighbourItems(t *testing.T) {
e := NewExecutor("/tmp")
e.clients["host1"] = NewMockSSHClient()