feat(ansible): support include_role apply defaults
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
57bc50002e
commit
807751ebe7
5 changed files with 157 additions and 1 deletions
56
executor.go
56
executor.go
|
|
@ -469,8 +469,9 @@ func (e *Executor) runRole(ctx context.Context, hosts []string, roleRef *RoleRef
|
|||
// Execute tasks
|
||||
for _, task := range tasks {
|
||||
effectiveTask := task
|
||||
e.applyRoleTaskDefaults(&effectiveTask, roleRef.Apply)
|
||||
if len(roleRef.Tags) > 0 {
|
||||
effectiveTask.Tags = mergeStringSlices(roleRef.Tags, task.Tags)
|
||||
effectiveTask.Tags = mergeStringSlices(roleRef.Tags, effectiveTask.Tags)
|
||||
}
|
||||
if err := e.runTaskOnHosts(ctx, eligibleHosts, &effectiveTask, play); err != nil {
|
||||
// Restore vars
|
||||
|
|
@ -1532,6 +1533,7 @@ func (e *Executor) resolveIncludeRoleRef(host string, task *Task) *RoleRef {
|
|||
|
||||
var roleName, tasksFrom, defaultsFrom, varsFrom string
|
||||
var roleVars map[string]any
|
||||
var apply *TaskApply
|
||||
|
||||
if task.IncludeRole != nil {
|
||||
roleName = task.IncludeRole.Name
|
||||
|
|
@ -1539,12 +1541,14 @@ func (e *Executor) resolveIncludeRoleRef(host string, task *Task) *RoleRef {
|
|||
defaultsFrom = task.IncludeRole.DefaultsFrom
|
||||
varsFrom = task.IncludeRole.VarsFrom
|
||||
roleVars = task.IncludeRole.Vars
|
||||
apply = task.IncludeRole.Apply
|
||||
} else if task.ImportRole != nil {
|
||||
roleName = task.ImportRole.Name
|
||||
tasksFrom = task.ImportRole.TasksFrom
|
||||
defaultsFrom = task.ImportRole.DefaultsFrom
|
||||
varsFrom = task.ImportRole.VarsFrom
|
||||
roleVars = task.ImportRole.Vars
|
||||
apply = task.ImportRole.Apply
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1560,6 +1564,7 @@ func (e *Executor) resolveIncludeRoleRef(host string, task *Task) *RoleRef {
|
|||
DefaultsFrom: e.templateString(defaultsFrom, host, task),
|
||||
VarsFrom: e.templateString(varsFrom, host, task),
|
||||
Vars: renderedVars,
|
||||
Apply: apply,
|
||||
When: task.When,
|
||||
Tags: task.Tags,
|
||||
}
|
||||
|
|
@ -1581,6 +1586,55 @@ func mergeTaskVars(parent, child map[string]any) map[string]any {
|
|||
return merged
|
||||
}
|
||||
|
||||
func mergeStringMap(parent, child map[string]string) map[string]string {
|
||||
if len(parent) == 0 && len(child) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
merged := make(map[string]string, len(parent)+len(child))
|
||||
for k, v := range parent {
|
||||
merged[k] = v
|
||||
}
|
||||
for k, v := range child {
|
||||
merged[k] = v
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
func (e *Executor) applyRoleTaskDefaults(task *Task, apply *TaskApply) {
|
||||
if task == nil || apply == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(apply.Tags) > 0 {
|
||||
task.Tags = mergeStringSlices(apply.Tags, task.Tags)
|
||||
}
|
||||
if len(apply.Vars) > 0 {
|
||||
task.Vars = mergeTaskVars(apply.Vars, task.Vars)
|
||||
}
|
||||
if len(apply.Environment) > 0 {
|
||||
task.Environment = mergeStringMap(apply.Environment, task.Environment)
|
||||
}
|
||||
if apply.Become != nil && task.Become == nil {
|
||||
task.Become = apply.Become
|
||||
}
|
||||
if apply.BecomeUser != "" && task.BecomeUser == "" {
|
||||
task.BecomeUser = apply.BecomeUser
|
||||
}
|
||||
if apply.Delegate != "" && task.Delegate == "" {
|
||||
task.Delegate = apply.Delegate
|
||||
}
|
||||
if apply.RunOnce && !task.RunOnce {
|
||||
task.RunOnce = true
|
||||
}
|
||||
if apply.NoLog {
|
||||
task.NoLog = true
|
||||
}
|
||||
if apply.IgnoreErrors {
|
||||
task.IgnoreErrors = true
|
||||
}
|
||||
}
|
||||
|
||||
// getHosts returns hosts matching the pattern.
|
||||
func (e *Executor) getHosts(pattern string) []string {
|
||||
if e.inventory == nil {
|
||||
|
|
|
|||
|
|
@ -775,6 +775,7 @@ func TestExecutorExtra_RunIncludeRole_Good_InheritsTaskVars(t *testing.T) {
|
|||
DefaultsFrom string `yaml:"defaults_from,omitempty"`
|
||||
VarsFrom string `yaml:"vars_from,omitempty"`
|
||||
Vars map[string]any `yaml:"vars,omitempty"`
|
||||
Apply *TaskApply `yaml:"apply,omitempty"`
|
||||
}{
|
||||
Name: "demo",
|
||||
},
|
||||
|
|
@ -785,6 +786,73 @@ func TestExecutorExtra_RunIncludeRole_Good_InheritsTaskVars(t *testing.T) {
|
|||
assert.Equal(t, "hello from role", e.results["localhost"]["role_result"].Msg)
|
||||
}
|
||||
|
||||
func TestExecutorExtra_RunIncludeRole_Good_AppliesRoleDefaults(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, writeTestFile(joinPath(dir, "roles", "app", "tasks", "main.yml"), []byte(`---
|
||||
- name: Applied role task
|
||||
vars:
|
||||
role_message: from-task
|
||||
shell: printf '%s|%s|%s' "$APP_ENV" "{{ apply_message }}" "{{ role_message }}"
|
||||
register: role_result
|
||||
`), 0644))
|
||||
|
||||
e := NewExecutor(dir)
|
||||
e.SetInventoryDirect(&Inventory{
|
||||
All: &InventoryGroup{
|
||||
Hosts: map[string]*Host{
|
||||
"localhost": {},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
gatherFacts := false
|
||||
play := &Play{
|
||||
Hosts: "localhost",
|
||||
Connection: "local",
|
||||
GatherFacts: &gatherFacts,
|
||||
}
|
||||
|
||||
var started *Task
|
||||
e.OnTaskStart = func(host string, task *Task) {
|
||||
if task.Name == "Applied role task" {
|
||||
started = task
|
||||
}
|
||||
}
|
||||
|
||||
// Re-run with callback attached so we can inspect the merged task state.
|
||||
require.NoError(t, e.runTaskOnHosts(context.Background(), []string{"localhost"}, &Task{
|
||||
Name: "Load role with apply",
|
||||
IncludeRole: &struct {
|
||||
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"`
|
||||
Apply *TaskApply `yaml:"apply,omitempty"`
|
||||
}{
|
||||
Name: "app",
|
||||
Apply: &TaskApply{
|
||||
Tags: []string{"role-apply"},
|
||||
Vars: map[string]any{
|
||||
"apply_message": "from-apply",
|
||||
"role_message": "from-apply",
|
||||
},
|
||||
Environment: map[string]string{
|
||||
"APP_ENV": "production",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, play))
|
||||
|
||||
require.NotNil(t, started)
|
||||
assert.Contains(t, started.Tags, "role-apply")
|
||||
assert.Equal(t, "production", started.Environment["APP_ENV"])
|
||||
assert.Equal(t, "from-apply", started.Vars["apply_message"])
|
||||
assert.Equal(t, "from-task", started.Vars["role_message"])
|
||||
require.NotNil(t, e.results["localhost"]["role_result"])
|
||||
assert.Equal(t, "production|from-apply|from-task", e.results["localhost"]["role_result"].Stdout)
|
||||
}
|
||||
|
||||
func TestExecutorExtra_GetHostsIter_Good(t *testing.T) {
|
||||
inv := &Inventory{
|
||||
All: &InventoryGroup{
|
||||
|
|
|
|||
|
|
@ -662,6 +662,7 @@ func TestExecutor_RunIncludeRole_Good_TemplatesRoleName(t *testing.T) {
|
|||
DefaultsFrom string `yaml:"defaults_from,omitempty"`
|
||||
VarsFrom string `yaml:"vars_from,omitempty"`
|
||||
Vars map[string]any `yaml:"vars,omitempty"`
|
||||
Apply *TaskApply `yaml:"apply,omitempty"`
|
||||
}{
|
||||
Name: "{{ role_name }}",
|
||||
},
|
||||
|
|
|
|||
20
types.go
20
types.go
|
|
@ -51,6 +51,7 @@ type RoleRef struct {
|
|||
DefaultsFrom string `yaml:"defaults_from,omitempty"`
|
||||
VarsFrom string `yaml:"vars_from,omitempty"`
|
||||
Vars map[string]any `yaml:"vars,omitempty"`
|
||||
Apply *TaskApply `yaml:"apply,omitempty"`
|
||||
When any `yaml:"when,omitempty"`
|
||||
Tags []string `yaml:"tags,omitempty"`
|
||||
}
|
||||
|
|
@ -128,6 +129,7 @@ type Task struct {
|
|||
DefaultsFrom string `yaml:"defaults_from,omitempty"`
|
||||
VarsFrom string `yaml:"vars_from,omitempty"`
|
||||
Vars map[string]any `yaml:"vars,omitempty"`
|
||||
Apply *TaskApply `yaml:"apply,omitempty"`
|
||||
} `yaml:"include_role,omitempty"`
|
||||
ImportRole *struct {
|
||||
Name string `yaml:"name"`
|
||||
|
|
@ -135,6 +137,7 @@ type Task struct {
|
|||
DefaultsFrom string `yaml:"defaults_from,omitempty"`
|
||||
VarsFrom string `yaml:"vars_from,omitempty"`
|
||||
Vars map[string]any `yaml:"vars,omitempty"`
|
||||
Apply *TaskApply `yaml:"apply,omitempty"`
|
||||
} `yaml:"import_role,omitempty"`
|
||||
|
||||
// Raw YAML for module extraction
|
||||
|
|
@ -154,6 +157,23 @@ type LoopControl struct {
|
|||
Extended bool `yaml:"extended,omitempty"`
|
||||
}
|
||||
|
||||
// TaskApply captures role-level task defaults from include_role/import_role.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// apply := TaskApply{Tags: []string{"deploy"}}
|
||||
type TaskApply struct {
|
||||
Tags []string `yaml:"tags,omitempty"`
|
||||
Vars map[string]any `yaml:"vars,omitempty"`
|
||||
Environment map[string]string `yaml:"environment,omitempty"`
|
||||
Become *bool `yaml:"become,omitempty"`
|
||||
BecomeUser string `yaml:"become_user,omitempty"`
|
||||
Delegate string `yaml:"delegate_to,omitempty"`
|
||||
RunOnce bool `yaml:"run_once,omitempty"`
|
||||
NoLog bool `yaml:"no_log,omitempty"`
|
||||
IgnoreErrors bool `yaml:"ignore_errors,omitempty"`
|
||||
}
|
||||
|
||||
// TaskResult holds the result of executing a task.
|
||||
//
|
||||
// Example:
|
||||
|
|
|
|||
|
|
@ -561,6 +561,13 @@ include_role:
|
|||
tasks_from: setup.yml
|
||||
defaults_from: defaults.yml
|
||||
vars_from: vars.yml
|
||||
apply:
|
||||
tags:
|
||||
- deploy
|
||||
become: true
|
||||
become_user: root
|
||||
environment:
|
||||
APP_ENV: production
|
||||
`
|
||||
var task Task
|
||||
err := yaml.Unmarshal([]byte(input), &task)
|
||||
|
|
@ -571,6 +578,12 @@ include_role:
|
|||
assert.Equal(t, "setup.yml", task.IncludeRole.TasksFrom)
|
||||
assert.Equal(t, "defaults.yml", task.IncludeRole.DefaultsFrom)
|
||||
assert.Equal(t, "vars.yml", task.IncludeRole.VarsFrom)
|
||||
require.NotNil(t, task.IncludeRole.Apply)
|
||||
assert.Equal(t, []string{"deploy"}, task.IncludeRole.Apply.Tags)
|
||||
require.NotNil(t, task.IncludeRole.Apply.Become)
|
||||
assert.True(t, *task.IncludeRole.Apply.Become)
|
||||
assert.Equal(t, "root", task.IncludeRole.Apply.BecomeUser)
|
||||
assert.Equal(t, "production", task.IncludeRole.Apply.Environment["APP_ENV"])
|
||||
}
|
||||
|
||||
func TestTypes_Task_UnmarshalYAML_Good_BecomeFields(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue