From d969cc92057d3253f3fd8ddb801b1a2bbc2589a9 Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 22:03:43 +0000 Subject: [PATCH] Resolve local paths from playbook base --- executor.go | 10 ++++++++++ modules.go | 6 ++++++ modules_adv_test.go | 20 ++++++++++++++++++++ modules_cmd_test.go | 19 +++++++++++++++++++ 4 files changed, 55 insertions(+) diff --git a/executor.go b/executor.go index 07e8e25..2027f95 100644 --- a/executor.go +++ b/executor.go @@ -1258,6 +1258,16 @@ func (e *Executor) getClient(host string, play *Play) (sshExecutorClient, error) return client, nil } +// resolveLocalPath resolves a local file path relative to the executor's base +// path when possible. +func (e *Executor) resolveLocalPath(path string) string { + if path == "" || e == nil || e.parser == nil { + return path + } + + return e.parser.resolvePath(path) +} + // gatherFacts collects facts from a host. func (e *Executor) gatherFacts(ctx context.Context, host string, play *Play) error { client, err := e.getClient(host, play) diff --git a/modules.go b/modules.go index d64ee22..a0d9ad4 100644 --- a/modules.go +++ b/modules.go @@ -339,6 +339,7 @@ func (e *Executor) moduleScript(ctx context.Context, client sshExecutorClient, a } // Read local script + script = e.resolveLocalPath(script) data, err := coreio.Local.Read(script) if err != nil { return nil, coreerr.E("Executor.moduleScript", "read script", err) @@ -370,6 +371,7 @@ func (e *Executor) moduleCopy(ctx context.Context, client sshExecutorClient, arg var err error if src := getStringArg(args, "src", ""); src != "" { + src = e.resolveLocalPath(src) content, err = coreio.Local.Read(src) if err != nil { return nil, coreerr.E("Executor.moduleCopy", "read src", err) @@ -424,6 +426,7 @@ func (e *Executor) moduleTemplate(ctx context.Context, client sshExecutorClient, } // Process template + src = e.resolveLocalPath(src) content, err := e.TemplateFile(src, host, task) if err != nil { return nil, coreerr.E("Executor.moduleTemplate", "template", err) @@ -1576,6 +1579,7 @@ func (e *Executor) moduleUnarchive(ctx context.Context, client sshExecutorClient var cmd string if !remote { // Upload local file first + src = e.resolveLocalPath(src) data, err := coreio.Local.Read(src) if err != nil { return nil, coreerr.E("Executor.moduleUnarchive", "read src", err) @@ -1984,6 +1988,7 @@ func (e *Executor) moduleIncludeVars(args map[string]any) (*TaskResult, error) { } if file != "" { + file = e.resolveLocalPath(file) sources = append(sources, file) if err := loadFile(file); err != nil { return nil, err @@ -1991,6 +1996,7 @@ func (e *Executor) moduleIncludeVars(args map[string]any) (*TaskResult, error) { } if dir != "" { + dir = e.resolveLocalPath(dir) entries, err := os.ReadDir(dir) if err != nil { return nil, coreerr.E("Executor.moduleIncludeVars", "read vars dir", err) diff --git a/modules_adv_test.go b/modules_adv_test.go index 4dc085f..2b4b8fa 100644 --- a/modules_adv_test.go +++ b/modules_adv_test.go @@ -858,6 +858,26 @@ func TestModulesAdv_ModuleIncludeVars_Good_LoadDirectoryWithMerge(t *testing.T) assert.Equal(t, 2, nested["b"]) } +func TestModulesAdv_ModuleIncludeVars_Good_ResolvesRelativePathsAgainstBasePath(t *testing.T) { + dir := t.TempDir() + require.NoError(t, writeTestFile(joinPath(dir, "vars.yml"), []byte("app_name: demo\n"), 0644)) + require.NoError(t, writeTestFile(joinPath(dir, "vars", "01-extra.yaml"), []byte("app_port: 8080\n"), 0644)) + + e := NewExecutor(dir) + + result, err := e.moduleIncludeVars(map[string]any{ + "file": "vars.yml", + "dir": "vars", + }) + + require.NoError(t, err) + assert.True(t, result.Changed) + assert.Contains(t, result.Msg, "vars.yml") + assert.Contains(t, result.Msg, joinPath(dir, "vars", "01-extra.yaml")) + assert.Equal(t, "demo", e.vars["app_name"]) + assert.Equal(t, 8080, e.vars["app_port"]) +} + // --- sysctl module --- func TestModulesAdv_ModuleSysctl_Good_ReloadsAfterPersisting(t *testing.T) { diff --git a/modules_cmd_test.go b/modules_cmd_test.go index 3ef5167..2ecc146 100644 --- a/modules_cmd_test.go +++ b/modules_cmd_test.go @@ -127,6 +127,25 @@ func TestModulesCmd_MockSSHClient_Good_BecomeTracking(t *testing.T) { assert.Equal(t, "secret", mock.becomePass) } +func TestModulesCmd_ModuleScript_Good_RelativePathResolvedAgainstBasePath(t *testing.T) { + dir := t.TempDir() + scriptPath := joinPath(dir, "scripts", "deploy.sh") + require.NoError(t, writeTestFile(scriptPath, []byte("echo deploy"), 0755)) + + e := NewExecutor(dir) + mock := NewMockSSHClient() + mock.expectCommand("echo deploy", "deploy\n", "", 0) + + result, err := e.moduleScript(context.Background(), mock, map[string]any{ + "_raw_params": "scripts/deploy.sh", + }) + + require.NoError(t, err) + assert.True(t, result.Changed) + assert.Equal(t, "deploy\n", result.Stdout) + assert.True(t, mock.hasExecuted("echo deploy")) +} + func TestModulesCmd_MockSSHClient_Good_HasExecuted(t *testing.T) { mock := NewMockSSHClient() _, _, _, _ = mock.Run(nil, "systemctl restart nginx")