From 5951f74f273dc4a36a79895060187e7358487341 Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 21:46:52 +0000 Subject: [PATCH] Resolve include task paths relative to playbooks --- executor_extra_test.go | 38 ++++++++++++++++++++++++++++++++++++++ parser.go | 21 +++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/executor_extra_test.go b/executor_extra_test.go index d87c69f..9aa0718 100644 --- a/executor_extra_test.go +++ b/executor_extra_test.go @@ -619,6 +619,44 @@ func TestExecutorExtra_ParseTasksIter_Bad_InvalidFile(t *testing.T) { assert.Error(t, err) } +func TestExecutorExtra_RunIncludeTasks_Good_RelativePath(t *testing.T) { + dir := t.TempDir() + includedPath := joinPath(dir, "included.yml") + yaml := `- name: Included first task + debug: + msg: first + +- name: Included second task + debug: + msg: second +` + require.NoError(t, writeTestFile(includedPath, []byte(yaml), 0644)) + + gatherFacts := false + play := &Play{ + Name: "Include tasks", + Hosts: "localhost", + GatherFacts: &gatherFacts, + Tasks: []Task{ + { + Name: "Load included tasks", + IncludeTasks: "included.yml", + }, + }, + } + + e := NewExecutor(dir) + var started []string + e.OnTaskStart = func(host string, task *Task) { + started = append(started, host+":"+task.Name) + } + + require.NoError(t, e.runPlay(context.Background(), play)) + + assert.Contains(t, started, "localhost:Included first task") + assert.Contains(t, started, "localhost:Included second task") +} + func TestExecutorExtra_GetHostsIter_Good(t *testing.T) { inv := &Inventory{ All: &InventoryGroup{ diff --git a/parser.go b/parser.go index 94f244d..e7f26af 100644 --- a/parser.go +++ b/parser.go @@ -126,6 +126,8 @@ func (p *Parser) ParseInventory(path string) (*Inventory, error) { // // tasks, err := parser.ParseTasks("/workspace/roles/web/tasks/main.yml") func (p *Parser) ParseTasks(path string) ([]Task, error) { + path = p.resolvePath(path) + data, err := coreio.Local.Read(path) if err != nil { return nil, coreerr.E("Parser.ParseTasks", "read tasks", err) @@ -145,6 +147,25 @@ func (p *Parser) ParseTasks(path string) ([]Task, error) { return tasks, nil } +// resolvePath resolves a possibly relative path against the parser base path. +func (p *Parser) resolvePath(path string) string { + if path == "" || pathIsAbs(path) || p.basePath == "" { + return path + } + + candidates := []string{ + joinPath(p.basePath, path), + path, + } + for _, candidate := range candidates { + if coreio.Local.Exists(candidate) { + return candidate + } + } + + return joinPath(p.basePath, path) +} + // ParseTasksIter returns an iterator for tasks in a tasks file. // // Example: