feat(ansible): recurse include_vars directories

This commit is contained in:
Virgil 2026-04-01 23:27:19 +00:00
parent efa2ac3ea1
commit bfa9a8d0ba
2 changed files with 94 additions and 15 deletions

View file

@ -2115,6 +2115,7 @@ func (e *Executor) moduleIncludeVars(args map[string]any) (*TaskResult, error) {
dir := getStringArg(args, "dir", "")
name := getStringArg(args, "name", "")
hashBehaviour := lower(getStringArg(args, "hash_behaviour", "replace"))
depth := getIntArg(args, "depth", 0)
if file == "" && dir == "" {
return &TaskResult{Changed: false}, nil
@ -2147,24 +2148,11 @@ func (e *Executor) moduleIncludeVars(args map[string]any) (*TaskResult, error) {
if dir != "" {
dir = e.resolveLocalPath(dir)
entries, err := os.ReadDir(dir)
files, err := collectIncludeVarsFiles(dir, depth)
if err != nil {
return nil, coreerr.E("Executor.moduleIncludeVars", "read vars dir", err)
return nil, err
}
var files []string
for _, entry := range entries {
if entry.IsDir() {
continue
}
ext := lower(filepath.Ext(entry.Name()))
if ext == ".yml" || ext == ".yaml" {
files = append(files, joinPath(dir, entry.Name()))
}
}
sort.Strings(files)
for _, path := range files {
sources = append(sources, path)
if err := loadFile(path); err != nil {
@ -2187,6 +2175,53 @@ func (e *Executor) moduleIncludeVars(args map[string]any) (*TaskResult, error) {
return &TaskResult{Changed: true, Msg: msg}, nil
}
func collectIncludeVarsFiles(dir string, depth int) ([]string, error) {
info, err := os.Stat(dir)
if err != nil {
return nil, coreerr.E("Executor.moduleIncludeVars", "read vars dir", err)
}
if !info.IsDir() {
return nil, coreerr.E("Executor.moduleIncludeVars", "read vars dir: not a directory", nil)
}
type dirEntry struct {
path string
depth int
}
var files []string
stack := []dirEntry{{path: dir, depth: 0}}
for len(stack) > 0 {
current := stack[len(stack)-1]
stack = stack[:len(stack)-1]
entries, err := os.ReadDir(current.path)
if err != nil {
return nil, coreerr.E("Executor.moduleIncludeVars", "read vars dir", err)
}
for i := len(entries) - 1; i >= 0; i-- {
entry := entries[i]
fullPath := joinPath(current.path, entry.Name())
if entry.IsDir() {
if depth == 0 || current.depth < depth {
stack = append(stack, dirEntry{path: fullPath, depth: current.depth + 1})
}
continue
}
ext := lower(filepath.Ext(entry.Name()))
if ext == ".yml" || ext == ".yaml" {
files = append(files, fullPath)
}
}
}
sort.Strings(files)
return files, nil
}
func mergeVars(dst, src map[string]any, mergeMaps bool) {
if dst == nil || src == nil {
return

View file

@ -954,6 +954,50 @@ func TestModulesAdv_ModuleIncludeVars_Good_ResolvesRelativePathsAgainstBasePath(
assert.Equal(t, 8080, e.vars["app_port"])
}
func TestModulesAdv_ModuleIncludeVars_Good_RecursesIntoNestedDirectories(t *testing.T) {
dir := t.TempDir()
require.NoError(t, writeTestFile(joinPath(dir, "01-root.yml"), []byte("root_value: root\n"), 0644))
require.NoError(t, writeTestFile(joinPath(dir, "nested", "02-child.yaml"), []byte("child_value: child\n"), 0644))
require.NoError(t, writeTestFile(joinPath(dir, "nested", "deep", "03-grandchild.yml"), []byte("grandchild_value: grandchild\n"), 0644))
e := NewExecutor("/tmp")
result, err := e.moduleIncludeVars(map[string]any{
"dir": dir,
})
require.NoError(t, err)
assert.True(t, result.Changed)
assert.Equal(t, "root", e.vars["root_value"])
assert.Equal(t, "child", e.vars["child_value"])
assert.Equal(t, "grandchild", e.vars["grandchild_value"])
assert.Contains(t, result.Msg, joinPath(dir, "01-root.yml"))
assert.Contains(t, result.Msg, joinPath(dir, "nested", "02-child.yaml"))
assert.Contains(t, result.Msg, joinPath(dir, "nested", "deep", "03-grandchild.yml"))
}
func TestModulesAdv_ModuleIncludeVars_Good_RespectsDepthLimit(t *testing.T) {
dir := t.TempDir()
require.NoError(t, writeTestFile(joinPath(dir, "01-root.yml"), []byte("root_value: root\n"), 0644))
require.NoError(t, writeTestFile(joinPath(dir, "nested", "02-child.yaml"), []byte("child_value: child\n"), 0644))
require.NoError(t, writeTestFile(joinPath(dir, "nested", "deep", "03-grandchild.yml"), []byte("grandchild_value: grandchild\n"), 0644))
e := NewExecutor("/tmp")
result, err := e.moduleIncludeVars(map[string]any{
"dir": dir,
"depth": 1,
})
require.NoError(t, err)
assert.True(t, result.Changed)
assert.Equal(t, "root", e.vars["root_value"])
assert.Equal(t, "child", e.vars["child_value"])
_, hasGrandchild := e.vars["grandchild_value"]
assert.False(t, hasGrandchild)
assert.NotContains(t, result.Msg, joinPath(dir, "nested", "deep", "03-grandchild.yml"))
}
// --- sysctl module ---
func TestModulesAdv_ModuleSysctl_Good_ReloadsAfterPersisting(t *testing.T) {