From d1682f6345787e8fa5cb85aa55ef05adf419592a Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 03:15:52 +0000 Subject: [PATCH] feat(ansible): accept inventory directories Co-Authored-By: Virgil --- executor_extra_test.go | 22 ++++++++++++++++++++++ parser.go | 19 +++++++++++++++++++ parser_test.go | 23 +++++++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/executor_extra_test.go b/executor_extra_test.go index a95b10f..865fa5d 100644 --- a/executor_extra_test.go +++ b/executor_extra_test.go @@ -2,6 +2,7 @@ package ansible import ( "context" + "os" "testing" "github.com/stretchr/testify/assert" @@ -594,6 +595,27 @@ func TestExecutorExtra_SetInventory_Good(t *testing.T) { assert.Len(t, e.inventory.All.Hosts, 2) } +func TestExecutorExtra_SetInventory_Good_Directory(t *testing.T) { + dir := t.TempDir() + inventoryDir := joinPath(dir, "inventory") + require.NoError(t, os.MkdirAll(inventoryDir, 0755)) + + invPath := joinPath(inventoryDir, "inventory.yml") + yaml := `all: + hosts: + web1: + ansible_host: 10.0.0.1 +` + require.NoError(t, writeTestFile(invPath, []byte(yaml), 0644)) + + e := NewExecutor(dir) + err := e.SetInventory(inventoryDir) + + require.NoError(t, err) + assert.NotNil(t, e.inventory) + assert.Contains(t, e.inventory.All.Hosts, "web1") +} + func TestExecutorExtra_SetInventory_Bad_FileNotFound(t *testing.T) { e := NewExecutor("/tmp") err := e.SetInventory("/nonexistent/inventory.yml") diff --git a/parser.go b/parser.go index 202dc71..f1f2b9f 100644 --- a/parser.go +++ b/parser.go @@ -109,6 +109,8 @@ func (p *Parser) ParsePlaybookIter(path string) (iter.Seq[Play], error) { // // inv, err := parser.ParseInventory("/workspace/inventory.yml") func (p *Parser) ParseInventory(path string) (*Inventory, error) { + path = p.resolveInventoryPath(path) + data, err := coreio.Local.Read(path) if err != nil { return nil, coreerr.E("Parser.ParseInventory", "read inventory", err) @@ -122,6 +124,23 @@ func (p *Parser) ParseInventory(path string) (*Inventory, error) { return &inv, nil } +// resolveInventoryPath resolves inventory directories to a concrete file. +func (p *Parser) resolveInventoryPath(path string) string { + path = p.resolvePath(path) + if path == "" || !coreio.Local.Exists(path) || !coreio.Local.IsDir(path) { + return path + } + + for _, name := range []string{"inventory.yml", "hosts.yml", "inventory.yaml", "hosts.yaml"} { + candidate := joinPath(path, name) + if coreio.Local.Exists(candidate) { + return candidate + } + } + + return path +} + // ParseTasks parses a tasks file (used by include_tasks). // // Example: diff --git a/parser_test.go b/parser_test.go index c9c1cf6..7a23235 100644 --- a/parser_test.go +++ b/parser_test.go @@ -495,6 +495,29 @@ all: assert.Equal(t, "192.168.1.11", inv.All.Hosts["web2"].AnsibleHost) } +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) +} + func TestParser_ParseInventory_Good_WithGroups(t *testing.T) { dir := t.TempDir() path := joinPath(dir, "inventory.yml")