feat(ansible): support indexed loop items
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
8130be049a
commit
92634bf561
4 changed files with 94 additions and 7 deletions
26
executor.go
26
executor.go
|
|
@ -1965,12 +1965,26 @@ func lookupNestedValue(value any, path string) (any, bool) {
|
|||
|
||||
current := value
|
||||
for _, segment := range split(path, ".") {
|
||||
next, ok := current.(map[string]any)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
current, ok = next[segment]
|
||||
if !ok {
|
||||
switch next := current.(type) {
|
||||
case map[string]any:
|
||||
var ok bool
|
||||
current, ok = next[segment]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
case []any:
|
||||
index, err := strconv.Atoi(segment)
|
||||
if err != nil || index < 0 || index >= len(next) {
|
||||
return nil, false
|
||||
}
|
||||
current = next[index]
|
||||
case []string:
|
||||
index, err := strconv.Atoi(segment)
|
||||
if err != nil || index < 0 || index >= len(next) {
|
||||
return nil, false
|
||||
}
|
||||
current = next[index]
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -639,6 +639,33 @@ func TestExecutor_RunTaskOnHost_Good_LoopFromWithDictItems(t *testing.T) {
|
|||
assert.Equal(t, "beta=two", result.Results[1].Msg)
|
||||
}
|
||||
|
||||
func TestExecutor_RunTaskOnHost_Good_LoopFromWithIndexedItems(t *testing.T) {
|
||||
e := NewExecutor("/tmp")
|
||||
e.clients["host1"] = NewMockSSHClient()
|
||||
|
||||
task := &Task{
|
||||
Name: "Indexed loop",
|
||||
Module: "debug",
|
||||
Args: map[string]any{
|
||||
"msg": "{{ item.0 }}={{ item.1 }}",
|
||||
},
|
||||
Loop: []any{
|
||||
[]any{0, "apple"},
|
||||
[]any{1, "banana"},
|
||||
},
|
||||
Register: "indexed_loop_result",
|
||||
}
|
||||
|
||||
err := e.runTaskOnHosts(context.Background(), []string{"host1"}, task, &Play{})
|
||||
require.NoError(t, err)
|
||||
|
||||
result := e.results["host1"]["indexed_loop_result"]
|
||||
require.NotNil(t, result)
|
||||
require.Len(t, result.Results, 2)
|
||||
assert.Equal(t, "0=apple", result.Results[0].Msg)
|
||||
assert.Equal(t, "1=banana", result.Results[1].Msg)
|
||||
}
|
||||
|
||||
func TestExecutor_RunTaskWithRetries_Good_UntilSuccess(t *testing.T) {
|
||||
e := NewExecutor("/tmp")
|
||||
attempts := 0
|
||||
|
|
|
|||
20
parser.go
20
parser.go
|
|
@ -348,7 +348,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
|||
"retries": true, "delay": true, "until": true,
|
||||
"include_tasks": true, "import_tasks": true,
|
||||
"include_role": true, "import_role": true,
|
||||
"with_items": true, "with_dict": true, "with_file": true,
|
||||
"with_items": true, "with_dict": true, "with_indexed_items": true, "with_file": true,
|
||||
}
|
||||
|
||||
for key, val := range m {
|
||||
|
|
@ -413,6 +413,24 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Handle with_indexed_items as a loop of [index, value] pairs.
|
||||
if indexed, ok := m["with_indexed_items"]; ok && t.Loop == nil {
|
||||
switch v := indexed.(type) {
|
||||
case []any:
|
||||
items := make([]any, 0, len(v))
|
||||
for i, item := range v {
|
||||
items = append(items, []any{i, item})
|
||||
}
|
||||
t.Loop = items
|
||||
case []string:
|
||||
items := make([]any, 0, len(v))
|
||||
for i, item := range v {
|
||||
items = append(items, []any{i, item})
|
||||
}
|
||||
t.Loop = items
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve with_file so the executor can resolve file contents at runtime.
|
||||
if files, ok := m["with_file"]; ok && t.WithFile == nil {
|
||||
t.WithFile = files
|
||||
|
|
|
|||
|
|
@ -204,6 +204,34 @@ with_dict:
|
|||
assert.Equal(t, "two", second["value"])
|
||||
}
|
||||
|
||||
func TestTypes_Task_UnmarshalYAML_Good_WithIndexedItems(t *testing.T) {
|
||||
input := `
|
||||
name: Indexed loop
|
||||
debug:
|
||||
msg: "{{ item.0 }}={{ item.1 }}"
|
||||
with_indexed_items:
|
||||
- apple
|
||||
- banana
|
||||
`
|
||||
var task Task
|
||||
err := yaml.Unmarshal([]byte(input), &task)
|
||||
|
||||
require.NoError(t, err)
|
||||
items, ok := task.Loop.([]any)
|
||||
require.True(t, ok)
|
||||
require.Len(t, items, 2)
|
||||
|
||||
first, ok := items[0].([]any)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, 0, first[0])
|
||||
assert.Equal(t, "apple", first[1])
|
||||
|
||||
second, ok := items[1].([]any)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, 1, second[0])
|
||||
assert.Equal(t, "banana", second[1])
|
||||
}
|
||||
|
||||
func TestTypes_Task_UnmarshalYAML_Good_WithFile(t *testing.T) {
|
||||
input := `
|
||||
name: Read files
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue