feat(ansible): support play vars_files

This commit is contained in:
Virgil 2026-04-01 23:05:24 +00:00
parent a80c2a2096
commit 9e7219782a
3 changed files with 84 additions and 0 deletions

View file

@ -18,6 +18,7 @@ import (
coreio "dappco.re/go/core/io"
coreerr "dappco.re/go/core/log"
"gopkg.in/yaml.v3"
)
var errEndPlay = errors.New("end play")
@ -183,6 +184,9 @@ func (e *Executor) runPlay(ctx context.Context, play *Play) error {
e.endedHosts = make(map[string]bool)
// Merge play vars
if err := e.loadPlayVarsFiles(play); err != nil {
return err
}
for k, v := range play.Vars {
e.vars[k] = v
}
@ -279,6 +283,48 @@ func (e *Executor) runPlay(ctx context.Context, play *Play) error {
return nil
}
// loadPlayVarsFiles loads any play-level vars_files entries and merges them
// into the play's Vars map before execution begins.
func (e *Executor) loadPlayVarsFiles(play *Play) error {
if play == nil {
return nil
}
files := normalizeStringList(play.VarsFiles)
if len(files) == 0 {
return nil
}
merged := make(map[string]any)
for _, file := range files {
resolved := e.resolveLocalPath(file)
data, err := coreio.Local.Read(resolved)
if err != nil {
return coreerr.E("Executor.loadPlayVarsFiles", "read vars file", err)
}
var vars map[string]any
if err := yaml.Unmarshal([]byte(data), &vars); err != nil {
return coreerr.E("Executor.loadPlayVarsFiles", "parse vars file", err)
}
mergeVars(merged, vars, false)
}
if len(merged) == 0 {
return nil
}
if play.Vars == nil {
play.Vars = make(map[string]any)
}
mergeVars(merged, play.Vars, false)
play.Vars = merged
return nil
}
// splitSerialHosts splits a host list into serial batches.
func splitSerialHosts(hosts []string, serial any) [][]string {
batchSize := resolveSerialBatchSize(serial, len(hosts))

View file

@ -249,6 +249,43 @@ func TestExecutor_RunTaskOnHost_Good_DelegateToUsesDelegatedClient(t *testing.T)
assert.Equal(t, 1, mock.commandCount())
}
func TestExecutor_Run_Good_VarsFilesMergeIntoPlayVars(t *testing.T) {
dir := t.TempDir()
require.NoError(t, writeTestFile(joinPath(dir, "vars", "common.yml"), []byte(`---
http_port: 8080
app_name: base
environment: staging
`), 0644))
require.NoError(t, writeTestFile(joinPath(dir, "vars", "override.yml"), []byte(`---
app_name: demo
`), 0644))
playbookPath := joinPath(dir, "playbook.yml")
require.NoError(t, writeTestFile(playbookPath, []byte(`---
- name: Vars files
hosts: localhost
gather_facts: false
vars:
environment: prod
vars_files:
- vars/common.yml
- vars/override.yml
tasks:
- name: Show merged vars
debug:
msg: "{{ http_port }} {{ app_name }} {{ environment }}"
register: vars_result
`), 0644))
e := NewExecutor(dir)
require.NoError(t, e.Run(context.Background(), playbookPath))
require.NotNil(t, e.results["localhost"])
require.NotNil(t, e.results["localhost"]["vars_result"])
assert.Equal(t, "8080 demo prod", e.results["localhost"]["vars_result"].Msg)
}
func TestExecutor_RunTaskOnHosts_Good_WithFileUsesFileContents(t *testing.T) {
dir := t.TempDir()
require.NoError(t, writeTestFile(joinPath(dir, "fragments", "hello.txt"), []byte("hello from file"), 0644))

View file

@ -27,6 +27,7 @@ type Play struct {
BecomeUser string `yaml:"become_user,omitempty"`
GatherFacts *bool `yaml:"gather_facts,omitempty"`
Vars map[string]any `yaml:"vars,omitempty"`
VarsFiles any `yaml:"vars_files,omitempty"` // string or []string
PreTasks []Task `yaml:"pre_tasks,omitempty"`
Tasks []Task `yaml:"tasks,omitempty"`
PostTasks []Task `yaml:"post_tasks,omitempty"`