fix(ansible): support include_tasks apply defaults
Some checks are pending
CI / test (push) Waiting to run
CI / auto-fix (push) Waiting to run
CI / auto-merge (push) Waiting to run

This commit is contained in:
Virgil 2026-04-03 09:52:23 +00:00
parent 0e813a93ca
commit 6f5d1659cd
5 changed files with 98 additions and 10 deletions

View file

@ -1938,8 +1938,9 @@ func (e *Executor) runIncludeTasks(ctx context.Context, hosts []string, task *Ta
for _, t := range tasks {
effectiveTask := t
effectiveTask.Vars = mergeTaskVars(task.Vars, t.Vars)
e.applyRoleTaskDefaults(&effectiveTask, task.Apply)
if len(effectiveTask.Vars) > 0 {
effectiveTask.Vars = e.templateArgs(effectiveTask.Vars, targetHost, task)
effectiveTask.Vars = e.templateArgs(effectiveTask.Vars, targetHost, &effectiveTask)
}
if len(task.Tags) > 0 {
effectiveTask.Tags = mergeStringSlices(task.Tags, effectiveTask.Tags)

View file

@ -12,6 +12,26 @@ import (
"gopkg.in/yaml.v3"
)
type becomeCall struct {
become bool
user string
password string
}
type trackingMockClient struct {
*MockSSHClient
becomeCalls []becomeCall
}
func newTrackingMockClient() *trackingMockClient {
return &trackingMockClient{MockSSHClient: NewMockSSHClient()}
}
func (c *trackingMockClient) SetBecome(become bool, user, password string) {
c.becomeCalls = append(c.becomeCalls, becomeCall{become: become, user: user, password: password})
c.MockSSHClient.SetBecome(become, user, password)
}
// --- NewExecutor ---
func TestExecutor_NewExecutor_Good(t *testing.T) {
@ -746,6 +766,47 @@ func TestExecutor_RunIncludeRole_Good_TemplatesRoleName(t *testing.T) {
assert.Equal(t, "role ran", e.results["localhost"]["role_result"].Msg)
}
func TestExecutor_RunIncludeTasks_Good_AppliesTaskDefaultsToChildren(t *testing.T) {
dir := t.TempDir()
require.NoError(t, writeTestFile(joinPath(dir, "tasks", "child.yml"), []byte(`---
- name: included shell task
shell: echo "{{ included_value }}"
register: child_result
`), 0644))
e := NewExecutor(dir)
e.SetInventoryDirect(&Inventory{
All: &InventoryGroup{
Hosts: map[string]*Host{
"host1": {},
},
},
})
mock := newTrackingMockClient()
e.clients["host1"] = mock
become := true
mock.expectCommand(`echo "from-apply"`, "from-apply\n", "", 0)
err := e.runTaskOnHosts(context.Background(), []string{"host1"}, &Task{
Name: "include child tasks",
IncludeTasks: "tasks/child.yml",
Apply: &TaskApply{
Vars: map[string]any{
"included_value": "from-apply",
},
Become: &become,
BecomeUser: "root",
},
}, &Play{})
require.NoError(t, err)
require.NotNil(t, e.results["host1"]["child_result"])
assert.Equal(t, "from-apply\n", e.results["host1"]["child_result"].Stdout)
assert.True(t, mock.hasExecuted(`echo "from-apply"`))
require.NotEmpty(t, mock.becomeCalls)
assert.Contains(t, mock.becomeCalls, becomeCall{become: true, user: "root", password: ""})
}
func TestExecutor_RunPlay_Good_AppliesPlayTagsToTasks(t *testing.T) {
e := NewExecutor("/tmp")
e.Tags = []string{"deploy"}

View file

@ -427,6 +427,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
"retries": true, "delay": true, "until": true,
"action": true, "local_action": true,
"include_tasks": true, "import_tasks": true,
"apply": true,
"include_role": true, "import_role": true, "public": true,
"with_items": true, "with_dict": true, "with_indexed_items": true, "with_nested": true, "with_together": true, "with_subelements": true, "with_file": true, "with_fileglob": true, "with_sequence": true,
}

View file

@ -122,15 +122,16 @@ type Task struct {
Until string `yaml:"until,omitempty"`
// Include/import directives
IncludeTasks string `yaml:"include_tasks,omitempty"`
ImportTasks string `yaml:"import_tasks,omitempty"`
WithFile any `yaml:"with_file,omitempty"`
WithFileGlob any `yaml:"with_fileglob,omitempty"`
WithSequence any `yaml:"with_sequence,omitempty"`
WithTogether any `yaml:"with_together,omitempty"`
WithSubelements any `yaml:"with_subelements,omitempty"`
IncludeRole *RoleRef `yaml:"include_role,omitempty"`
ImportRole *RoleRef `yaml:"import_role,omitempty"`
IncludeTasks string `yaml:"include_tasks,omitempty"`
ImportTasks string `yaml:"import_tasks,omitempty"`
Apply *TaskApply `yaml:"apply,omitempty"`
WithFile any `yaml:"with_file,omitempty"`
WithFileGlob any `yaml:"with_fileglob,omitempty"`
WithSequence any `yaml:"with_sequence,omitempty"`
WithTogether any `yaml:"with_together,omitempty"`
WithSubelements any `yaml:"with_subelements,omitempty"`
IncludeRole *RoleRef `yaml:"include_role,omitempty"`
ImportRole *RoleRef `yaml:"import_role,omitempty"`
// Raw YAML for module extraction
raw map[string]any

View file

@ -605,6 +605,30 @@ include_tasks: other-tasks.yml
assert.Equal(t, "other-tasks.yml", task.IncludeTasks)
}
func TestTypes_Task_UnmarshalYAML_Good_IncludeTasksApply(t *testing.T) {
input := `
name: Include tasks
include_tasks: other-tasks.yml
apply:
tags:
- deploy
become: true
become_user: root
environment:
APP_ENV: production
`
var task Task
err := yaml.Unmarshal([]byte(input), &task)
require.NoError(t, err)
require.NotNil(t, task.Apply)
assert.Equal(t, []string{"deploy"}, task.Apply.Tags)
require.NotNil(t, task.Apply.Become)
assert.True(t, *task.Apply.Become)
assert.Equal(t, "root", task.Apply.BecomeUser)
assert.Equal(t, "production", task.Apply.Environment["APP_ENV"])
}
func TestTypes_Task_UnmarshalYAML_Good_IncludeRole(t *testing.T) {
input := `
name: Include role