2026-03-09 11:37:27 +00:00
|
|
|
package ansible
|
|
|
|
|
|
|
|
|
|
import (
|
2026-04-01 20:54:36 +00:00
|
|
|
"os"
|
2026-03-09 11:37:27 +00:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// --- ParsePlaybook ---
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParsePlaybook_Good_SimplePlay(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "playbook.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
- name: Configure webserver
|
|
|
|
|
hosts: webservers
|
|
|
|
|
become: true
|
|
|
|
|
tasks:
|
|
|
|
|
- name: Install nginx
|
|
|
|
|
apt:
|
|
|
|
|
name: nginx
|
|
|
|
|
state: present
|
|
|
|
|
`
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
plays, err := p.ParsePlaybook(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Len(t, plays, 1)
|
|
|
|
|
assert.Equal(t, "Configure webserver", plays[0].Name)
|
|
|
|
|
assert.Equal(t, "webservers", plays[0].Hosts)
|
|
|
|
|
assert.True(t, plays[0].Become)
|
|
|
|
|
require.Len(t, plays[0].Tasks, 1)
|
|
|
|
|
assert.Equal(t, "Install nginx", plays[0].Tasks[0].Name)
|
|
|
|
|
assert.Equal(t, "apt", plays[0].Tasks[0].Module)
|
|
|
|
|
assert.Equal(t, "nginx", plays[0].Tasks[0].Args["name"])
|
|
|
|
|
assert.Equal(t, "present", plays[0].Tasks[0].Args["state"])
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParsePlaybook_Good_MultiplePlays(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "playbook.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
- name: Play one
|
|
|
|
|
hosts: all
|
|
|
|
|
tasks:
|
|
|
|
|
- name: Say hello
|
|
|
|
|
debug:
|
|
|
|
|
msg: "Hello"
|
|
|
|
|
|
|
|
|
|
- name: Play two
|
|
|
|
|
hosts: localhost
|
|
|
|
|
connection: local
|
|
|
|
|
tasks:
|
|
|
|
|
- name: Say goodbye
|
|
|
|
|
debug:
|
|
|
|
|
msg: "Goodbye"
|
|
|
|
|
`
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
plays, err := p.ParsePlaybook(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Len(t, plays, 2)
|
|
|
|
|
assert.Equal(t, "Play one", plays[0].Name)
|
|
|
|
|
assert.Equal(t, "all", plays[0].Hosts)
|
|
|
|
|
assert.Equal(t, "Play two", plays[1].Name)
|
|
|
|
|
assert.Equal(t, "localhost", plays[1].Hosts)
|
|
|
|
|
assert.Equal(t, "local", plays[1].Connection)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 20:54:36 +00:00
|
|
|
func TestParser_ParsePlaybook_Good_ImportPlaybook(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
mainPath := joinPath(dir, "site.yml")
|
|
|
|
|
importDir := joinPath(dir, "plays")
|
|
|
|
|
importPath := joinPath(importDir, "web.yml")
|
|
|
|
|
|
|
|
|
|
yamlMain := `---
|
|
|
|
|
- name: Before import
|
|
|
|
|
hosts: all
|
|
|
|
|
tasks:
|
|
|
|
|
- name: Say before
|
|
|
|
|
debug:
|
|
|
|
|
msg: "before"
|
|
|
|
|
|
|
|
|
|
- import_playbook: plays/web.yml
|
|
|
|
|
|
|
|
|
|
- name: After import
|
|
|
|
|
hosts: all
|
|
|
|
|
tasks:
|
|
|
|
|
- name: Say after
|
|
|
|
|
debug:
|
|
|
|
|
msg: "after"
|
|
|
|
|
`
|
|
|
|
|
yamlImported := `---
|
|
|
|
|
- name: Imported play
|
|
|
|
|
hosts: webservers
|
|
|
|
|
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, 3)
|
|
|
|
|
assert.Equal(t, "Before import", plays[0].Name)
|
|
|
|
|
assert.Equal(t, "Imported play", plays[1].Name)
|
|
|
|
|
assert.Equal(t, "After import", plays[2].Name)
|
|
|
|
|
assert.Equal(t, "webservers", plays[1].Hosts)
|
|
|
|
|
assert.Len(t, plays[1].Tasks, 1)
|
|
|
|
|
assert.Equal(t, "Say imported", plays[1].Tasks[0].Name)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 14:50:41 +00:00
|
|
|
func TestParser_ParsePlaybook_Good_TemplatedImportPlaybook(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
mainPath := joinPath(dir, "site.yml")
|
|
|
|
|
importDir := joinPath(dir, "plays")
|
|
|
|
|
importPath := joinPath(importDir, "web.yml")
|
|
|
|
|
|
|
|
|
|
yamlMain := `---
|
|
|
|
|
- import_playbook: "{{ playbook_dir }}/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)
|
|
|
|
|
p.vars["playbook_dir"] = "plays"
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParsePlaybook_Good_WithVars(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "playbook.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
- name: With vars
|
|
|
|
|
hosts: all
|
|
|
|
|
vars:
|
|
|
|
|
http_port: 8080
|
|
|
|
|
app_name: myapp
|
|
|
|
|
tasks:
|
|
|
|
|
- name: Print port
|
|
|
|
|
debug:
|
|
|
|
|
msg: "Port is {{ http_port }}"
|
|
|
|
|
`
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
plays, err := p.ParsePlaybook(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Len(t, plays, 1)
|
|
|
|
|
assert.Equal(t, 8080, plays[0].Vars["http_port"])
|
|
|
|
|
assert.Equal(t, "myapp", plays[0].Vars["app_name"])
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParsePlaybook_Good_PrePostTasks(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "playbook.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
- name: Full lifecycle
|
|
|
|
|
hosts: all
|
|
|
|
|
pre_tasks:
|
|
|
|
|
- name: Pre task
|
|
|
|
|
debug:
|
|
|
|
|
msg: "pre"
|
|
|
|
|
tasks:
|
|
|
|
|
- name: Main task
|
|
|
|
|
debug:
|
|
|
|
|
msg: "main"
|
|
|
|
|
post_tasks:
|
|
|
|
|
- name: Post task
|
|
|
|
|
debug:
|
|
|
|
|
msg: "post"
|
|
|
|
|
`
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
plays, err := p.ParsePlaybook(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Len(t, plays, 1)
|
|
|
|
|
assert.Len(t, plays[0].PreTasks, 1)
|
|
|
|
|
assert.Len(t, plays[0].Tasks, 1)
|
|
|
|
|
assert.Len(t, plays[0].PostTasks, 1)
|
|
|
|
|
assert.Equal(t, "Pre task", plays[0].PreTasks[0].Name)
|
|
|
|
|
assert.Equal(t, "Main task", plays[0].Tasks[0].Name)
|
|
|
|
|
assert.Equal(t, "Post task", plays[0].PostTasks[0].Name)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParsePlaybook_Good_Handlers(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "playbook.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
- name: With handlers
|
|
|
|
|
hosts: all
|
|
|
|
|
tasks:
|
|
|
|
|
- name: Install package
|
|
|
|
|
apt:
|
|
|
|
|
name: nginx
|
|
|
|
|
notify: restart nginx
|
|
|
|
|
handlers:
|
|
|
|
|
- name: restart nginx
|
|
|
|
|
service:
|
|
|
|
|
name: nginx
|
|
|
|
|
state: restarted
|
|
|
|
|
`
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
plays, err := p.ParsePlaybook(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Len(t, plays, 1)
|
|
|
|
|
assert.Len(t, plays[0].Handlers, 1)
|
|
|
|
|
assert.Equal(t, "restart nginx", plays[0].Handlers[0].Name)
|
|
|
|
|
assert.Equal(t, "service", plays[0].Handlers[0].Module)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParsePlaybook_Good_ShellFreeForm(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "playbook.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
- name: Shell tasks
|
|
|
|
|
hosts: all
|
|
|
|
|
tasks:
|
|
|
|
|
- name: Run a command
|
|
|
|
|
shell: echo hello world
|
|
|
|
|
- name: Run raw command
|
|
|
|
|
command: ls -la /tmp
|
|
|
|
|
`
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
plays, err := p.ParsePlaybook(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Len(t, plays[0].Tasks, 2)
|
|
|
|
|
assert.Equal(t, "shell", plays[0].Tasks[0].Module)
|
|
|
|
|
assert.Equal(t, "echo hello world", plays[0].Tasks[0].Args["_raw_params"])
|
|
|
|
|
assert.Equal(t, "command", plays[0].Tasks[1].Module)
|
|
|
|
|
assert.Equal(t, "ls -la /tmp", plays[0].Tasks[1].Args["_raw_params"])
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParsePlaybook_Good_WithTags(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "playbook.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
- name: Tagged play
|
|
|
|
|
hosts: all
|
|
|
|
|
tags:
|
|
|
|
|
- setup
|
|
|
|
|
tasks:
|
|
|
|
|
- name: Tagged task
|
|
|
|
|
debug:
|
|
|
|
|
msg: "tagged"
|
|
|
|
|
tags:
|
|
|
|
|
- debug
|
|
|
|
|
- always
|
|
|
|
|
`
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
plays, err := p.ParsePlaybook(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, []string{"setup"}, plays[0].Tags)
|
|
|
|
|
assert.Equal(t, []string{"debug", "always"}, plays[0].Tasks[0].Tags)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParsePlaybook_Good_BlockRescueAlways(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "playbook.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
- name: With blocks
|
|
|
|
|
hosts: all
|
|
|
|
|
tasks:
|
|
|
|
|
- name: Protected block
|
|
|
|
|
block:
|
|
|
|
|
- name: Try this
|
|
|
|
|
shell: echo try
|
|
|
|
|
rescue:
|
|
|
|
|
- name: Handle error
|
|
|
|
|
debug:
|
|
|
|
|
msg: "rescued"
|
|
|
|
|
always:
|
|
|
|
|
- name: Always runs
|
|
|
|
|
debug:
|
|
|
|
|
msg: "always"
|
|
|
|
|
`
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
plays, err := p.ParsePlaybook(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
task := plays[0].Tasks[0]
|
|
|
|
|
assert.Len(t, task.Block, 1)
|
|
|
|
|
assert.Len(t, task.Rescue, 1)
|
|
|
|
|
assert.Len(t, task.Always, 1)
|
|
|
|
|
assert.Equal(t, "Try this", task.Block[0].Name)
|
|
|
|
|
assert.Equal(t, "Handle error", task.Rescue[0].Name)
|
|
|
|
|
assert.Equal(t, "Always runs", task.Always[0].Name)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParsePlaybook_Good_WithLoop(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "playbook.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
- name: Loop test
|
|
|
|
|
hosts: all
|
|
|
|
|
tasks:
|
|
|
|
|
- name: Install packages
|
|
|
|
|
apt:
|
|
|
|
|
name: "{{ item }}"
|
|
|
|
|
state: present
|
|
|
|
|
loop:
|
|
|
|
|
- vim
|
|
|
|
|
- curl
|
|
|
|
|
- git
|
|
|
|
|
`
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
plays, err := p.ParsePlaybook(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
task := plays[0].Tasks[0]
|
|
|
|
|
assert.Equal(t, "apt", task.Module)
|
|
|
|
|
items, ok := task.Loop.([]any)
|
|
|
|
|
require.True(t, ok)
|
|
|
|
|
assert.Len(t, items, 3)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParsePlaybook_Good_RoleRefs(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "playbook.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
- name: With roles
|
|
|
|
|
hosts: all
|
|
|
|
|
roles:
|
|
|
|
|
- common
|
|
|
|
|
- role: webserver
|
|
|
|
|
vars:
|
|
|
|
|
http_port: 80
|
|
|
|
|
tags:
|
|
|
|
|
- web
|
|
|
|
|
`
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
plays, err := p.ParsePlaybook(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Len(t, plays[0].Roles, 2)
|
|
|
|
|
assert.Equal(t, "common", plays[0].Roles[0].Role)
|
|
|
|
|
assert.Equal(t, "webserver", plays[0].Roles[1].Role)
|
|
|
|
|
assert.Equal(t, 80, plays[0].Roles[1].Vars["http_port"])
|
|
|
|
|
assert.Equal(t, []string{"web"}, plays[0].Roles[1].Tags)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParsePlaybook_Good_FullyQualifiedModules(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "playbook.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
- name: FQCN modules
|
|
|
|
|
hosts: all
|
|
|
|
|
tasks:
|
|
|
|
|
- name: Copy file
|
|
|
|
|
ansible.builtin.copy:
|
|
|
|
|
src: /tmp/foo
|
|
|
|
|
dest: /tmp/bar
|
|
|
|
|
- name: Run shell
|
|
|
|
|
ansible.builtin.shell: echo hello
|
|
|
|
|
`
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
plays, err := p.ParsePlaybook(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "ansible.builtin.copy", plays[0].Tasks[0].Module)
|
|
|
|
|
assert.Equal(t, "/tmp/foo", plays[0].Tasks[0].Args["src"])
|
|
|
|
|
assert.Equal(t, "ansible.builtin.shell", plays[0].Tasks[1].Module)
|
|
|
|
|
assert.Equal(t, "echo hello", plays[0].Tasks[1].Args["_raw_params"])
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParsePlaybook_Good_RegisterAndWhen(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "playbook.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
- name: Conditional play
|
|
|
|
|
hosts: all
|
|
|
|
|
tasks:
|
|
|
|
|
- name: Check file
|
|
|
|
|
stat:
|
|
|
|
|
path: /etc/nginx/nginx.conf
|
|
|
|
|
register: nginx_conf
|
|
|
|
|
- name: Show result
|
|
|
|
|
debug:
|
|
|
|
|
msg: "File exists"
|
|
|
|
|
when: nginx_conf.stat.exists
|
|
|
|
|
`
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
plays, err := p.ParsePlaybook(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "nginx_conf", plays[0].Tasks[0].Register)
|
|
|
|
|
assert.NotNil(t, plays[0].Tasks[1].When)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParsePlaybook_Good_EmptyPlaybook(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "playbook.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte("---\n[]"), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
plays, err := p.ParsePlaybook(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Empty(t, plays)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParsePlaybook_Bad_InvalidYAML(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "bad.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte("{{invalid yaml}}"), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
_, err := p.ParsePlaybook(path)
|
|
|
|
|
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "parse playbook")
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParsePlaybook_Bad_FileNotFound(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
p := NewParser(t.TempDir())
|
|
|
|
|
_, err := p.ParsePlaybook("/nonexistent/playbook.yml")
|
|
|
|
|
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "read playbook")
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParsePlaybook_Good_GatherFactsDisabled(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "playbook.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
- name: No facts
|
|
|
|
|
hosts: all
|
|
|
|
|
gather_facts: false
|
|
|
|
|
tasks: []
|
|
|
|
|
`
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
plays, err := p.ParsePlaybook(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NotNil(t, plays[0].GatherFacts)
|
|
|
|
|
assert.False(t, *plays[0].GatherFacts)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- ParseInventory ---
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParseInventory_Good_SimpleInventory(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "inventory.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
all:
|
|
|
|
|
hosts:
|
|
|
|
|
web1:
|
|
|
|
|
ansible_host: 192.168.1.10
|
|
|
|
|
web2:
|
|
|
|
|
ansible_host: 192.168.1.11
|
|
|
|
|
`
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
inv, err := p.ParseInventory(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NotNil(t, inv.All)
|
|
|
|
|
assert.Len(t, inv.All.Hosts, 2)
|
|
|
|
|
assert.Equal(t, "192.168.1.10", inv.All.Hosts["web1"].AnsibleHost)
|
|
|
|
|
assert.Equal(t, "192.168.1.11", inv.All.Hosts["web2"].AnsibleHost)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 03:15:52 +00:00
|
|
|
func TestParser_ParseInventory_Good_DirectoryInventory(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
inventoryDir := joinPath(dir, "inventory")
|
|
|
|
|
require.NoError(t, os.MkdirAll(inventoryDir, 0755))
|
|
|
|
|
|
|
|
|
|
path := joinPath(inventoryDir, "hosts.yml")
|
|
|
|
|
yaml := `---
|
|
|
|
|
all:
|
|
|
|
|
hosts:
|
|
|
|
|
web1:
|
|
|
|
|
ansible_host: 192.168.1.10
|
|
|
|
|
`
|
|
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
|
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
inv, err := p.ParseInventory(inventoryDir)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NotNil(t, inv.All)
|
|
|
|
|
require.Contains(t, inv.All.Hosts, "web1")
|
|
|
|
|
assert.Equal(t, "192.168.1.10", inv.All.Hosts["web1"].AnsibleHost)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParseInventory_Good_WithGroups(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "inventory.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
all:
|
|
|
|
|
children:
|
|
|
|
|
webservers:
|
|
|
|
|
hosts:
|
|
|
|
|
web1:
|
|
|
|
|
ansible_host: 10.0.0.1
|
|
|
|
|
web2:
|
|
|
|
|
ansible_host: 10.0.0.2
|
|
|
|
|
databases:
|
|
|
|
|
hosts:
|
|
|
|
|
db1:
|
|
|
|
|
ansible_host: 10.0.1.1
|
|
|
|
|
`
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
inv, err := p.ParseInventory(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NotNil(t, inv.All.Children["webservers"])
|
|
|
|
|
assert.Len(t, inv.All.Children["webservers"].Hosts, 2)
|
|
|
|
|
require.NotNil(t, inv.All.Children["databases"])
|
|
|
|
|
assert.Len(t, inv.All.Children["databases"].Hosts, 1)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 02:07:09 +00:00
|
|
|
func TestParser_ParseInventory_Good_TopLevelGroups(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
path := joinPath(dir, "inventory.yml")
|
|
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
webservers:
|
|
|
|
|
vars:
|
|
|
|
|
tier: web
|
|
|
|
|
hosts:
|
|
|
|
|
web1:
|
|
|
|
|
ansible_host: 10.0.0.1
|
|
|
|
|
web2:
|
|
|
|
|
ansible_host: 10.0.0.2
|
|
|
|
|
databases:
|
|
|
|
|
hosts:
|
|
|
|
|
db1:
|
|
|
|
|
ansible_host: 10.0.1.1
|
|
|
|
|
`
|
|
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
|
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
inv, err := p.ParseInventory(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NotNil(t, inv.All)
|
|
|
|
|
require.NotNil(t, inv.All.Children["webservers"])
|
|
|
|
|
require.NotNil(t, inv.All.Children["databases"])
|
|
|
|
|
assert.Len(t, inv.All.Children["webservers"].Hosts, 2)
|
|
|
|
|
assert.Len(t, inv.All.Children["databases"].Hosts, 1)
|
|
|
|
|
assert.Equal(t, "web", inv.All.Children["webservers"].Vars["tier"])
|
|
|
|
|
assert.ElementsMatch(t, []string{"web1", "web2", "db1"}, GetHosts(inv, "all"))
|
|
|
|
|
assert.Equal(t, []string{"web1", "web2"}, GetHosts(inv, "webservers"))
|
|
|
|
|
assert.Equal(t, "web", GetHostVars(inv, "web1")["tier"])
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParseInventory_Good_WithVars(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "inventory.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
all:
|
|
|
|
|
vars:
|
|
|
|
|
ansible_user: admin
|
|
|
|
|
children:
|
|
|
|
|
production:
|
|
|
|
|
vars:
|
|
|
|
|
env: prod
|
|
|
|
|
hosts:
|
|
|
|
|
prod1:
|
|
|
|
|
ansible_host: 10.0.0.1
|
|
|
|
|
ansible_port: 2222
|
|
|
|
|
`
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
inv, err := p.ParseInventory(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "admin", inv.All.Vars["ansible_user"])
|
|
|
|
|
assert.Equal(t, "prod", inv.All.Children["production"].Vars["env"])
|
|
|
|
|
assert.Equal(t, 2222, inv.All.Children["production"].Hosts["prod1"].AnsiblePort)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParseInventory_Bad_InvalidYAML(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "bad.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte("{{{bad"), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
_, err := p.ParseInventory(path)
|
|
|
|
|
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "parse inventory")
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParseInventory_Bad_FileNotFound(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
p := NewParser(t.TempDir())
|
|
|
|
|
_, err := p.ParseInventory("/nonexistent/inventory.yml")
|
|
|
|
|
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "read inventory")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- ParseTasks ---
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParseTasks_Good_TaskFile(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "tasks.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
yaml := `---
|
|
|
|
|
- name: First task
|
|
|
|
|
shell: echo first
|
|
|
|
|
- name: Second task
|
|
|
|
|
copy:
|
|
|
|
|
src: /tmp/a
|
|
|
|
|
dest: /tmp/b
|
|
|
|
|
`
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte(yaml), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
tasks, err := p.ParseTasks(path)
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Len(t, tasks, 2)
|
|
|
|
|
assert.Equal(t, "shell", tasks[0].Module)
|
|
|
|
|
assert.Equal(t, "echo first", tasks[0].Args["_raw_params"])
|
|
|
|
|
assert.Equal(t, "copy", tasks[1].Module)
|
|
|
|
|
assert.Equal(t, "/tmp/a", tasks[1].Args["src"])
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_ParseTasks_Bad_InvalidYAML(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
dir := t.TempDir()
|
2026-03-26 16:39:59 +00:00
|
|
|
path := joinPath(dir, "bad.yml")
|
2026-03-09 11:37:27 +00:00
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
require.NoError(t, writeTestFile(path, []byte("not: [valid: tasks"), 0644))
|
2026-03-09 11:37:27 +00:00
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
_, err := p.ParseTasks(path)
|
|
|
|
|
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 03:12:11 +00:00
|
|
|
func TestParser_ParseRole_Good_LoadsRoleVarsIntoParserContext(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
|
|
|
|
|
require.NoError(t, writeTestFile(joinPath(dir, "roles", "web", "tasks", "main.yml"), []byte(`---
|
|
|
|
|
- name: Role task
|
|
|
|
|
debug:
|
|
|
|
|
msg: "{{ role_default }} {{ role_value }} {{ shared_value }}"
|
|
|
|
|
`), 0644))
|
|
|
|
|
require.NoError(t, writeTestFile(joinPath(dir, "roles", "web", "defaults", "main.yml"), []byte(`---
|
|
|
|
|
role_default: default-value
|
|
|
|
|
shared_value: default-shared
|
|
|
|
|
`), 0644))
|
|
|
|
|
require.NoError(t, writeTestFile(joinPath(dir, "roles", "web", "vars", "main.yml"), []byte(`---
|
|
|
|
|
role_value: vars-value
|
|
|
|
|
shared_value: role-shared
|
|
|
|
|
`), 0644))
|
|
|
|
|
|
|
|
|
|
p := NewParser(dir)
|
|
|
|
|
p.vars["existing_value"] = "keep-me"
|
|
|
|
|
|
|
|
|
|
tasks, err := p.ParseRole("web", "main.yml")
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Len(t, tasks, 1)
|
|
|
|
|
assert.Equal(t, "debug", tasks[0].Module)
|
|
|
|
|
assert.Equal(t, "keep-me", p.vars["existing_value"])
|
|
|
|
|
assert.Equal(t, "default-value", p.vars["role_default"])
|
|
|
|
|
assert.Equal(t, "vars-value", p.vars["role_value"])
|
|
|
|
|
assert.Equal(t, "role-shared", p.vars["shared_value"])
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-09 11:37:27 +00:00
|
|
|
// --- GetHosts ---
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_GetHosts_Good_AllPattern(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
inv := &Inventory{
|
|
|
|
|
All: &InventoryGroup{
|
|
|
|
|
Hosts: map[string]*Host{
|
|
|
|
|
"host1": {},
|
|
|
|
|
"host2": {},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hosts := GetHosts(inv, "all")
|
|
|
|
|
assert.Len(t, hosts, 2)
|
|
|
|
|
assert.Contains(t, hosts, "host1")
|
|
|
|
|
assert.Contains(t, hosts, "host2")
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_GetHosts_Good_LocalhostPattern(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
inv := &Inventory{All: &InventoryGroup{}}
|
|
|
|
|
hosts := GetHosts(inv, "localhost")
|
|
|
|
|
assert.Equal(t, []string{"localhost"}, hosts)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_GetHosts_Good_GroupPattern(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
inv := &Inventory{
|
|
|
|
|
All: &InventoryGroup{
|
|
|
|
|
Children: map[string]*InventoryGroup{
|
|
|
|
|
"web": {
|
|
|
|
|
Hosts: map[string]*Host{
|
|
|
|
|
"web1": {},
|
|
|
|
|
"web2": {},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"db": {
|
|
|
|
|
Hosts: map[string]*Host{
|
|
|
|
|
"db1": {},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hosts := GetHosts(inv, "web")
|
|
|
|
|
assert.Len(t, hosts, 2)
|
|
|
|
|
assert.Contains(t, hosts, "web1")
|
|
|
|
|
assert.Contains(t, hosts, "web2")
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_GetHosts_Good_SpecificHost(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
inv := &Inventory{
|
|
|
|
|
All: &InventoryGroup{
|
|
|
|
|
Children: map[string]*InventoryGroup{
|
|
|
|
|
"servers": {
|
|
|
|
|
Hosts: map[string]*Host{
|
|
|
|
|
"myhost": {},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hosts := GetHosts(inv, "myhost")
|
|
|
|
|
assert.Equal(t, []string{"myhost"}, hosts)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 21:02:51 +00:00
|
|
|
func TestParser_GetHosts_Good_ColonUnionIntersectionExclusion(t *testing.T) {
|
|
|
|
|
inv := &Inventory{
|
|
|
|
|
All: &InventoryGroup{
|
|
|
|
|
Children: map[string]*InventoryGroup{
|
|
|
|
|
"web": {
|
|
|
|
|
Hosts: map[string]*Host{
|
|
|
|
|
"web1": {},
|
|
|
|
|
"web2": {},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"db": {
|
|
|
|
|
Hosts: map[string]*Host{
|
|
|
|
|
"db1": {},
|
|
|
|
|
"web2": {},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"canary": {
|
|
|
|
|
Hosts: map[string]*Host{
|
|
|
|
|
"web2": {},
|
|
|
|
|
"db1": {},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, []string{"web1", "web2", "db1"}, GetHosts(inv, "web:db"))
|
|
|
|
|
assert.Equal(t, []string{"web2"}, GetHosts(inv, "web:&db"))
|
|
|
|
|
assert.Equal(t, []string{"web1"}, GetHosts(inv, "web:!canary"))
|
|
|
|
|
assert.Equal(t, []string{"web1"}, GetHosts(inv, "web:db:!canary"))
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_GetHosts_Good_AllIncludesChildren(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
inv := &Inventory{
|
|
|
|
|
All: &InventoryGroup{
|
|
|
|
|
Hosts: map[string]*Host{"top": {}},
|
|
|
|
|
Children: map[string]*InventoryGroup{
|
|
|
|
|
"group1": {
|
|
|
|
|
Hosts: map[string]*Host{"child1": {}},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hosts := GetHosts(inv, "all")
|
|
|
|
|
assert.Len(t, hosts, 2)
|
|
|
|
|
assert.Contains(t, hosts, "top")
|
|
|
|
|
assert.Contains(t, hosts, "child1")
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_GetHosts_Bad_NoMatch(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
inv := &Inventory{
|
|
|
|
|
All: &InventoryGroup{
|
|
|
|
|
Hosts: map[string]*Host{"host1": {}},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hosts := GetHosts(inv, "nonexistent")
|
|
|
|
|
assert.Empty(t, hosts)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_GetHosts_Bad_NilGroup(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
inv := &Inventory{All: nil}
|
|
|
|
|
hosts := GetHosts(inv, "all")
|
|
|
|
|
assert.Empty(t, hosts)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- GetHostVars ---
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_GetHostVars_Good_DirectHost(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
inv := &Inventory{
|
|
|
|
|
All: &InventoryGroup{
|
|
|
|
|
Vars: map[string]any{"global_var": "global"},
|
|
|
|
|
Hosts: map[string]*Host{
|
|
|
|
|
"myhost": {
|
2026-04-02 14:31:01 +00:00
|
|
|
AnsibleHost: "10.0.0.1",
|
|
|
|
|
AnsiblePort: 2222,
|
|
|
|
|
AnsibleUser: "deploy",
|
|
|
|
|
AnsibleBecomePassword: "secret",
|
2026-03-09 11:37:27 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vars := GetHostVars(inv, "myhost")
|
|
|
|
|
assert.Equal(t, "10.0.0.1", vars["ansible_host"])
|
|
|
|
|
assert.Equal(t, 2222, vars["ansible_port"])
|
|
|
|
|
assert.Equal(t, "deploy", vars["ansible_user"])
|
2026-04-02 14:31:01 +00:00
|
|
|
assert.Equal(t, "secret", vars["ansible_become_password"])
|
2026-03-09 11:37:27 +00:00
|
|
|
assert.Equal(t, "global", vars["global_var"])
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_GetHostVars_Good_InheritedGroupVars(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
inv := &Inventory{
|
|
|
|
|
All: &InventoryGroup{
|
|
|
|
|
Vars: map[string]any{"level": "all"},
|
|
|
|
|
Children: map[string]*InventoryGroup{
|
|
|
|
|
"production": {
|
|
|
|
|
Vars: map[string]any{"env": "prod", "level": "group"},
|
|
|
|
|
Hosts: map[string]*Host{
|
|
|
|
|
"prod1": {
|
|
|
|
|
AnsibleHost: "10.0.0.1",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vars := GetHostVars(inv, "prod1")
|
|
|
|
|
assert.Equal(t, "10.0.0.1", vars["ansible_host"])
|
|
|
|
|
assert.Equal(t, "prod", vars["env"])
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_GetHostVars_Good_HostNotFound(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
inv := &Inventory{
|
|
|
|
|
All: &InventoryGroup{
|
|
|
|
|
Hosts: map[string]*Host{"other": {}},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vars := GetHostVars(inv, "nonexistent")
|
|
|
|
|
assert.Empty(t, vars)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- isModule ---
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_IsModule_Good_KnownModules(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
assert.True(t, isModule("shell"))
|
|
|
|
|
assert.True(t, isModule("command"))
|
|
|
|
|
assert.True(t, isModule("copy"))
|
|
|
|
|
assert.True(t, isModule("file"))
|
|
|
|
|
assert.True(t, isModule("apt"))
|
|
|
|
|
assert.True(t, isModule("service"))
|
|
|
|
|
assert.True(t, isModule("systemd"))
|
2026-04-02 00:43:32 +00:00
|
|
|
assert.True(t, isModule("rpm"))
|
2026-03-09 11:37:27 +00:00
|
|
|
assert.True(t, isModule("debug"))
|
|
|
|
|
assert.True(t, isModule("set_fact"))
|
2026-04-02 02:29:36 +00:00
|
|
|
assert.True(t, isModule("ping"))
|
2026-03-09 11:37:27 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_IsModule_Good_FQCN(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
assert.True(t, isModule("ansible.builtin.shell"))
|
|
|
|
|
assert.True(t, isModule("ansible.builtin.copy"))
|
|
|
|
|
assert.True(t, isModule("ansible.builtin.apt"))
|
2026-04-02 00:43:32 +00:00
|
|
|
assert.True(t, isModule("ansible.builtin.rpm"))
|
2026-03-09 11:37:27 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_IsModule_Good_DottedUnknown(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
// Any key with dots is considered a module
|
|
|
|
|
assert.True(t, isModule("community.general.ufw"))
|
|
|
|
|
assert.True(t, isModule("ansible.posix.authorized_key"))
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_IsModule_Bad_NotAModule(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
assert.False(t, isModule("some_random_key"))
|
|
|
|
|
assert.False(t, isModule("foobar"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- NormalizeModule ---
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_NormalizeModule_Good(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
assert.Equal(t, "ansible.builtin.shell", NormalizeModule("shell"))
|
|
|
|
|
assert.Equal(t, "ansible.builtin.copy", NormalizeModule("copy"))
|
|
|
|
|
assert.Equal(t, "ansible.builtin.apt", NormalizeModule("apt"))
|
2026-04-02 00:43:32 +00:00
|
|
|
assert.Equal(t, "ansible.builtin.rpm", NormalizeModule("rpm"))
|
2026-04-02 02:29:36 +00:00
|
|
|
assert.Equal(t, "ansible.builtin.ping", NormalizeModule("ping"))
|
2026-03-09 11:37:27 +00:00
|
|
|
}
|
|
|
|
|
|
2026-04-01 21:00:32 +00:00
|
|
|
func TestParser_NormalizeModule_Good_CommunityAliases(t *testing.T) {
|
|
|
|
|
assert.Equal(t, "ansible.posix.authorized_key", NormalizeModule("authorized_key"))
|
2026-04-02 01:47:29 +00:00
|
|
|
assert.Equal(t, "ansible.posix.authorized_key", NormalizeModule("ansible.builtin.authorized_key"))
|
2026-04-01 21:00:32 +00:00
|
|
|
assert.Equal(t, "community.general.ufw", NormalizeModule("ufw"))
|
2026-04-02 01:47:29 +00:00
|
|
|
assert.Equal(t, "community.general.ufw", NormalizeModule("ansible.builtin.ufw"))
|
2026-04-01 21:00:32 +00:00
|
|
|
assert.Equal(t, "community.docker.docker_compose", NormalizeModule("docker_compose"))
|
|
|
|
|
assert.Equal(t, "community.docker.docker_compose_v2", NormalizeModule("docker_compose_v2"))
|
2026-04-01 22:39:42 +00:00
|
|
|
assert.Equal(t, "community.docker.docker_compose", NormalizeModule("ansible.builtin.docker_compose"))
|
2026-04-02 14:35:19 +00:00
|
|
|
assert.Equal(t, "community.docker.docker_compose_v2", NormalizeModule("ansible.builtin.docker_compose_v2"))
|
2026-04-01 21:00:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_NormalizeModule_Good_AlreadyFQCN(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
assert.Equal(t, "ansible.builtin.shell", NormalizeModule("ansible.builtin.shell"))
|
|
|
|
|
assert.Equal(t, "community.general.ufw", NormalizeModule("community.general.ufw"))
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 13:57:01 +00:00
|
|
|
func TestParser_NormalizeModule_Good_LegacyNamespace(t *testing.T) {
|
|
|
|
|
assert.Equal(t, "ansible.builtin.command", NormalizeModule("ansible.legacy.command"))
|
|
|
|
|
assert.Equal(t, "ansible.posix.authorized_key", NormalizeModule("ansible.legacy.authorized_key"))
|
|
|
|
|
assert.Equal(t, "community.general.ufw", NormalizeModule("ansible.legacy.ufw"))
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-09 11:37:27 +00:00
|
|
|
// --- NewParser ---
|
|
|
|
|
|
2026-03-26 16:39:59 +00:00
|
|
|
func TestParser_NewParser_Good(t *testing.T) {
|
2026-03-09 11:37:27 +00:00
|
|
|
p := NewParser("/some/path")
|
|
|
|
|
assert.NotNil(t, p)
|
|
|
|
|
assert.Equal(t, "/some/path", p.basePath)
|
|
|
|
|
assert.NotNil(t, p.vars)
|
|
|
|
|
}
|