fix(ansible): accept FQCN include directives
Co-authored-by: Virgil <virgil@lethean.io>
This commit is contained in:
parent
8699d00933
commit
dd9ccc777c
4 changed files with 139 additions and 0 deletions
33
parser.go
33
parser.go
|
|
@ -427,11 +427,36 @@ 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,
|
||||
"ansible.builtin.include_tasks": true, "ansible.legacy.include_tasks": true,
|
||||
"ansible.builtin.import_tasks": true, "ansible.legacy.import_tasks": true,
|
||||
"apply": true,
|
||||
"include_role": true, "import_role": true, "public": true,
|
||||
"ansible.builtin.include_role": true, "ansible.legacy.include_role": true,
|
||||
"ansible.builtin.import_role": true, "ansible.legacy.import_role": 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,
|
||||
}
|
||||
|
||||
if value, ok := directiveValue(m, "include_tasks"); ok && t.IncludeTasks == "" {
|
||||
t.IncludeTasks = sprintf("%v", value)
|
||||
}
|
||||
if value, ok := directiveValue(m, "import_tasks"); ok && t.ImportTasks == "" {
|
||||
t.ImportTasks = sprintf("%v", value)
|
||||
}
|
||||
if value, ok := directiveValue(m, "include_role"); ok && t.IncludeRole == nil {
|
||||
var ref RoleRef
|
||||
if err := decodeYAMLValue(value, &ref); err != nil {
|
||||
return err
|
||||
}
|
||||
t.IncludeRole = &ref
|
||||
}
|
||||
if value, ok := directiveValue(m, "import_role"); ok && t.ImportRole == nil {
|
||||
var ref RoleRef
|
||||
if err := decodeYAMLValue(value, &ref); err != nil {
|
||||
return err
|
||||
}
|
||||
t.ImportRole = &ref
|
||||
}
|
||||
|
||||
for key, val := range m {
|
||||
if knownKeys[key] {
|
||||
continue
|
||||
|
|
@ -561,6 +586,14 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func decodeYAMLValue(value any, out any) error {
|
||||
data, err := yaml.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return yaml.Unmarshal(data, out)
|
||||
}
|
||||
|
||||
// parseActionSpec converts action/local_action values into a module name and
|
||||
// argument map.
|
||||
func parseActionSpec(value any) (string, map[string]any) {
|
||||
|
|
|
|||
|
|
@ -155,6 +155,36 @@ func TestParser_ParsePlaybook_Good_TemplatedImportPlaybook(t *testing.T) {
|
|||
assert.Equal(t, "all", plays[0].Hosts)
|
||||
}
|
||||
|
||||
func TestParser_ParsePlaybook_Good_FQCNImportPlaybook(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
mainPath := joinPath(dir, "site.yml")
|
||||
importDir := joinPath(dir, "plays")
|
||||
importPath := joinPath(importDir, "web.yml")
|
||||
|
||||
yamlMain := `---
|
||||
- ansible.builtin.import_playbook: plays/web.yml
|
||||
`
|
||||
yamlImported := `---
|
||||
- name: Imported play
|
||||
hosts: all
|
||||
tasks:
|
||||
- name: Say imported
|
||||
debug:
|
||||
msg: "imported"
|
||||
`
|
||||
require.NoError(t, os.MkdirAll(importDir, 0755))
|
||||
require.NoError(t, writeTestFile(mainPath, []byte(yamlMain), 0644))
|
||||
require.NoError(t, writeTestFile(importPath, []byte(yamlImported), 0644))
|
||||
|
||||
p := NewParser(dir)
|
||||
plays, err := p.ParsePlaybook(mainPath)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, plays, 1)
|
||||
assert.Equal(t, "Imported play", plays[0].Name)
|
||||
assert.Equal(t, "all", plays[0].Hosts)
|
||||
}
|
||||
|
||||
func TestParser_ParsePlaybook_Good_WithVars(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := joinPath(dir, "playbook.yml")
|
||||
|
|
|
|||
35
types.go
35
types.go
|
|
@ -44,6 +44,27 @@ type Play struct {
|
|||
MaxFailPercent int `yaml:"max_fail_percentage,omitempty"`
|
||||
}
|
||||
|
||||
// UnmarshalYAML handles play-level aliases such as ansible.builtin.import_playbook.
|
||||
func (p *Play) UnmarshalYAML(node *yaml.Node) error {
|
||||
type rawPlay Play
|
||||
var raw rawPlay
|
||||
if err := node.Decode(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
*p = Play(raw)
|
||||
|
||||
var fields map[string]any
|
||||
if err := node.Decode(&fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if value, ok := directiveValue(fields, "import_playbook"); ok && p.ImportPlaybook == "" {
|
||||
p.ImportPlaybook = sprintf("%v", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RoleRef represents a role reference in a play.
|
||||
//
|
||||
// Example:
|
||||
|
|
@ -90,6 +111,20 @@ func (r *RoleRef) UnmarshalYAML(unmarshal func(any) error) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func directiveValue(fields map[string]any, name string) (any, bool) {
|
||||
if fields == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
for _, key := range []string{name, "ansible.builtin." + name, "ansible.legacy." + name} {
|
||||
if value, ok := fields[key]; ok {
|
||||
return value, true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Task represents an Ansible task.
|
||||
//
|
||||
// Example:
|
||||
|
|
|
|||
|
|
@ -605,6 +605,18 @@ include_tasks: other-tasks.yml
|
|||
assert.Equal(t, "other-tasks.yml", task.IncludeTasks)
|
||||
}
|
||||
|
||||
func TestTypes_Task_UnmarshalYAML_Good_IncludeTasksFQCN(t *testing.T) {
|
||||
input := `
|
||||
name: Include tasks
|
||||
ansible.builtin.include_tasks: other-tasks.yml
|
||||
`
|
||||
var task Task
|
||||
err := yaml.Unmarshal([]byte(input), &task)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "other-tasks.yml", task.IncludeTasks)
|
||||
}
|
||||
|
||||
func TestTypes_Task_UnmarshalYAML_Good_IncludeTasksApply(t *testing.T) {
|
||||
input := `
|
||||
name: Include tasks
|
||||
|
|
@ -681,6 +693,22 @@ include_role: common
|
|||
assert.Equal(t, "common", task.IncludeRole.Role)
|
||||
}
|
||||
|
||||
func TestTypes_Task_UnmarshalYAML_Good_IncludeRoleFQCN(t *testing.T) {
|
||||
input := `
|
||||
name: Include role
|
||||
ansible.builtin.include_role:
|
||||
name: common
|
||||
tasks_from: setup.yml
|
||||
`
|
||||
var task Task
|
||||
err := yaml.Unmarshal([]byte(input), &task)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, task.IncludeRole)
|
||||
assert.Equal(t, "common", task.IncludeRole.Role)
|
||||
assert.Equal(t, "setup.yml", task.IncludeRole.TasksFrom)
|
||||
}
|
||||
|
||||
func TestTypes_Task_UnmarshalYAML_Good_ImportRoleStringForm(t *testing.T) {
|
||||
input := `
|
||||
name: Import role
|
||||
|
|
@ -694,6 +722,19 @@ import_role: common
|
|||
assert.Equal(t, "common", task.ImportRole.Role)
|
||||
}
|
||||
|
||||
func TestTypes_Task_UnmarshalYAML_Good_ImportRoleFQCN(t *testing.T) {
|
||||
input := `
|
||||
name: Import role
|
||||
ansible.builtin.import_role: common
|
||||
`
|
||||
var task Task
|
||||
err := yaml.Unmarshal([]byte(input), &task)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, task.ImportRole)
|
||||
assert.Equal(t, "common", task.ImportRole.Role)
|
||||
}
|
||||
|
||||
func TestTypes_Task_UnmarshalYAML_Good_BecomeFields(t *testing.T) {
|
||||
input := `
|
||||
name: Privileged task
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue