Support custom role defaults and vars files

This commit is contained in:
Virgil 2026-04-01 23:15:08 +00:00
parent 187f157435
commit 2e54726977
6 changed files with 111 additions and 24 deletions

View file

@ -431,7 +431,7 @@ func (e *Executor) runRole(ctx context.Context, hosts []string, roleRef *RoleRef
oldVars[k] = v
}
tasks, defaults, roleVars, err := e.parser.loadRoleData(roleRef.Role, roleRef.TasksFrom)
tasks, defaults, roleVars, err := e.parser.loadRoleData(roleRef.Role, roleRef.TasksFrom, roleRef.DefaultsFrom, roleRef.VarsFrom)
if err != nil {
e.vars = oldVars
return coreerr.E("executor.runRole", sprintf("parse role %s", roleRef.Role), err)
@ -1448,23 +1448,29 @@ func (e *Executor) runIncludeTasks(ctx context.Context, hosts []string, task *Ta
// runIncludeRole handles include_role/import_role.
func (e *Executor) runIncludeRole(ctx context.Context, hosts []string, task *Task, play *Play) error {
var roleName, tasksFrom string
var roleName, tasksFrom, defaultsFrom, varsFrom string
var roleVars map[string]any
if task.IncludeRole != nil {
roleName = task.IncludeRole.Name
tasksFrom = task.IncludeRole.TasksFrom
defaultsFrom = task.IncludeRole.DefaultsFrom
varsFrom = task.IncludeRole.VarsFrom
roleVars = task.IncludeRole.Vars
} else {
roleName = task.ImportRole.Name
tasksFrom = task.ImportRole.TasksFrom
defaultsFrom = task.ImportRole.DefaultsFrom
varsFrom = task.ImportRole.VarsFrom
roleVars = task.ImportRole.Vars
}
roleRef := &RoleRef{
Role: roleName,
TasksFrom: tasksFrom,
Vars: mergeTaskVars(roleVars, task.Vars),
Role: roleName,
TasksFrom: tasksFrom,
DefaultsFrom: defaultsFrom,
VarsFrom: varsFrom,
Vars: mergeTaskVars(roleVars, task.Vars),
}
return e.runRole(ctx, hosts, roleRef, play)

View file

@ -770,9 +770,11 @@ func TestExecutorExtra_RunIncludeRole_Good_InheritsTaskVars(t *testing.T) {
require.NoError(t, e.runTaskOnHosts(context.Background(), []string{"localhost"}, &Task{
Name: "Load role",
IncludeRole: &struct {
Name string `yaml:"name"`
TasksFrom string `yaml:"tasks_from,omitempty"`
Vars map[string]any `yaml:"vars,omitempty"`
Name string `yaml:"name"`
TasksFrom string `yaml:"tasks_from,omitempty"`
DefaultsFrom string `yaml:"defaults_from,omitempty"`
VarsFrom string `yaml:"vars_from,omitempty"`
Vars map[string]any `yaml:"vars,omitempty"`
}{
Name: "demo",
},

View file

@ -527,6 +527,52 @@ role_value: vars-value
assert.False(t, leaked)
}
func TestExecutor_RunRole_Good_UsesCustomRoleDefaultsAndVarsFiles(t *testing.T) {
dir := t.TempDir()
require.NoError(t, writeTestFile(joinPath(dir, "roles", "webserver", "tasks", "main.yml"), []byte(`---
- name: role file selector task
debug:
msg: "{{ role_value }}|{{ role_param }}"
register: role_result
`), 0644))
require.NoError(t, writeTestFile(joinPath(dir, "roles", "webserver", "defaults", "main.yml"), []byte(`---
role_value: default-main
`), 0644))
require.NoError(t, writeTestFile(joinPath(dir, "roles", "webserver", "defaults", "custom.yml"), []byte(`---
role_value: default-custom
`), 0644))
require.NoError(t, writeTestFile(joinPath(dir, "roles", "webserver", "vars", "main.yml"), []byte(`---
role_value: vars-main
`), 0644))
require.NoError(t, writeTestFile(joinPath(dir, "roles", "webserver", "vars", "custom.yml"), []byte(`---
role_value: vars-custom
`), 0644))
e := NewExecutor(dir)
e.SetInventoryDirect(&Inventory{
All: &InventoryGroup{
Hosts: map[string]*Host{
"host1": {},
},
},
})
e.clients["host1"] = NewMockSSHClient()
err := e.runRole(context.Background(), []string{"host1"}, &RoleRef{
Role: "webserver",
DefaultsFrom: "custom.yml",
VarsFrom: "custom.yml",
Vars: map[string]any{
"role_param": "include-value",
},
}, &Play{Connection: "local"})
require.NoError(t, err)
require.NotNil(t, e.results["host1"]["role_result"])
assert.Equal(t, "vars-custom|include-value", e.results["host1"]["role_result"].Msg)
}
func TestExecutor_RunPlay_Good_AppliesPlayTagsToTasks(t *testing.T) {
e := NewExecutor("/tmp")
e.Tags = []string{"deploy"}

View file

@ -191,14 +191,20 @@ func (p *Parser) ParseTasksIter(path string) (iter.Seq[Task], error) {
//
// tasks, err := parser.ParseRole("nginx", "main.yml")
func (p *Parser) ParseRole(name string, tasksFrom string) ([]Task, error) {
tasks, _, _, err := p.loadRoleData(name, tasksFrom)
tasks, _, _, err := p.loadRoleData(name, tasksFrom, "", "")
return tasks, err
}
func (p *Parser) loadRoleData(name string, tasksFrom string) ([]Task, map[string]any, map[string]any, error) {
func (p *Parser) loadRoleData(name string, tasksFrom string, defaultsFrom string, varsFrom string) ([]Task, map[string]any, map[string]any, error) {
if tasksFrom == "" {
tasksFrom = "main.yml"
}
if defaultsFrom == "" {
defaultsFrom = "main.yml"
}
if varsFrom == "" {
varsFrom = "main.yml"
}
// Search paths for roles (in order of precedence)
searchPaths := []string{
@ -230,7 +236,7 @@ func (p *Parser) loadRoleData(name string, tasksFrom string) ([]Task, map[string
defaults := make(map[string]any)
// Load role defaults
defaultsPath := joinPath(pathDir(pathDir(tasksPath)), "defaults", "main.yml")
defaultsPath := joinPath(pathDir(pathDir(tasksPath)), "defaults", defaultsFrom)
if data, err := coreio.Local.Read(defaultsPath); err == nil {
if yaml.Unmarshal([]byte(data), &defaults) != nil {
defaults = make(map[string]any)
@ -239,7 +245,7 @@ func (p *Parser) loadRoleData(name string, tasksFrom string) ([]Task, map[string
roleVars := make(map[string]any)
// Load role vars
varsPath := joinPath(pathDir(pathDir(tasksPath)), "vars", "main.yml")
varsPath := joinPath(pathDir(pathDir(tasksPath)), "vars", varsFrom)
if data, err := coreio.Local.Read(varsPath); err == nil {
if yaml.Unmarshal([]byte(data), &roleVars) != nil {
roleVars = make(map[string]any)

View file

@ -45,12 +45,14 @@ type Play struct {
//
// role := RoleRef{Role: "nginx", TasksFrom: "install.yml"}
type RoleRef struct {
Role string `yaml:"role,omitempty"`
Name string `yaml:"name,omitempty"` // Alternative to role
TasksFrom string `yaml:"tasks_from,omitempty"`
Vars map[string]any `yaml:"vars,omitempty"`
When any `yaml:"when,omitempty"`
Tags []string `yaml:"tags,omitempty"`
Role string `yaml:"role,omitempty"`
Name string `yaml:"name,omitempty"` // Alternative to role
TasksFrom string `yaml:"tasks_from,omitempty"`
DefaultsFrom string `yaml:"defaults_from,omitempty"`
VarsFrom string `yaml:"vars_from,omitempty"`
Vars map[string]any `yaml:"vars,omitempty"`
When any `yaml:"when,omitempty"`
Tags []string `yaml:"tags,omitempty"`
}
// UnmarshalYAML handles both string and struct role refs.
@ -120,14 +122,18 @@ type Task struct {
WithFileGlob any `yaml:"with_fileglob,omitempty"`
WithSequence any `yaml:"with_sequence,omitempty"`
IncludeRole *struct {
Name string `yaml:"name"`
TasksFrom string `yaml:"tasks_from,omitempty"`
Vars map[string]any `yaml:"vars,omitempty"`
Name string `yaml:"name"`
TasksFrom string `yaml:"tasks_from,omitempty"`
DefaultsFrom string `yaml:"defaults_from,omitempty"`
VarsFrom string `yaml:"vars_from,omitempty"`
Vars map[string]any `yaml:"vars,omitempty"`
} `yaml:"include_role,omitempty"`
ImportRole *struct {
Name string `yaml:"name"`
TasksFrom string `yaml:"tasks_from,omitempty"`
Vars map[string]any `yaml:"vars,omitempty"`
Name string `yaml:"name"`
TasksFrom string `yaml:"tasks_from,omitempty"`
DefaultsFrom string `yaml:"defaults_from,omitempty"`
VarsFrom string `yaml:"vars_from,omitempty"`
Vars map[string]any `yaml:"vars,omitempty"`
} `yaml:"import_role,omitempty"`
// Raw YAML for module extraction

View file

@ -63,6 +63,23 @@ when: ansible_os_family == "Debian"
assert.NotNil(t, ref.When)
}
func TestTypes_RoleRef_UnmarshalYAML_Good_CustomRoleFiles(t *testing.T) {
input := `
name: web
tasks_from: setup.yml
defaults_from: custom-defaults.yml
vars_from: custom-vars.yml
`
var ref RoleRef
err := yaml.Unmarshal([]byte(input), &ref)
require.NoError(t, err)
assert.Equal(t, "web", ref.Role)
assert.Equal(t, "setup.yml", ref.TasksFrom)
assert.Equal(t, "custom-defaults.yml", ref.DefaultsFrom)
assert.Equal(t, "custom-vars.yml", ref.VarsFrom)
}
// --- Task UnmarshalYAML ---
func TestTypes_Task_UnmarshalYAML_Good_ModuleWithArgs(t *testing.T) {
@ -446,6 +463,8 @@ name: Include role
include_role:
name: common
tasks_from: setup.yml
defaults_from: defaults.yml
vars_from: vars.yml
`
var task Task
err := yaml.Unmarshal([]byte(input), &task)
@ -454,6 +473,8 @@ include_role:
require.NotNil(t, task.IncludeRole)
assert.Equal(t, "common", task.IncludeRole.Name)
assert.Equal(t, "setup.yml", task.IncludeRole.TasksFrom)
assert.Equal(t, "defaults.yml", task.IncludeRole.DefaultsFrom)
assert.Equal(t, "vars.yml", task.IncludeRole.VarsFrom)
}
func TestTypes_Task_UnmarshalYAML_Good_BecomeFields(t *testing.T) {