From b2aad914f10feb5dd68342de150785ca7f2383a2 Mon Sep 17 00:00:00 2001 From: Nick Fagerlund Date: Fri, 12 Dec 2025 11:05:32 -0800 Subject: [PATCH] stacks: Make path.module and path.root relative, to match documentation (#37982) * stacks: Make path.module and path.root relative, to match documentation Previously, we were just handing off the absolute path produced by the sourcebundle struct. But that's no good, because the value doesn't stay consistent between plan and apply when running under tfc-agent. (It uses working directories named after the unique job ID.) It also doesn't match the documentation, which describes these as relative paths. This was preventing people from uploading module-provided files to create aws lambda functions, for example. This commit addresses that by converting the source-bundle provided module path to a relative path (relative to Terraform's working directory). In tfc-agent for stacks runs, that ends up being the directory directly above the sourcebundle directory, and all the paths below that are consistent between plan and apply. * I think this is how the changelog thing works? --- .changes/v1.14/BUG FIXES-20251209-230000.yaml | 5 ++++ internal/configs/source_bundle_parser.go | 26 ++++++++++++++++++- .../internal/stackeval/planning_test.go | 23 +++++----------- 3 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 .changes/v1.14/BUG FIXES-20251209-230000.yaml diff --git a/.changes/v1.14/BUG FIXES-20251209-230000.yaml b/.changes/v1.14/BUG FIXES-20251209-230000.yaml new file mode 100644 index 0000000000..d378f24610 --- /dev/null +++ b/.changes/v1.14/BUG FIXES-20251209-230000.yaml @@ -0,0 +1,5 @@ +kind: BUG FIXES +body: 'stacks: change absolute paths in path.module/path.root to be relative, as documented' +time: 2025-12-09T23:00:00.316597+00:00 +custom: + Issue: "37982" diff --git a/internal/configs/source_bundle_parser.go b/internal/configs/source_bundle_parser.go index edac0cf014..092f40296c 100644 --- a/internal/configs/source_bundle_parser.go +++ b/internal/configs/source_bundle_parser.go @@ -67,7 +67,31 @@ func (p *SourceBundleParser) LoadConfigDir(source sourceaddrs.FinalSource) (*Mod }) return nil, diags } - mod.SourceDir = sourceDir + + // The result of sources.LocalPathForSource is an absolute path, but we + // don't actually want to pass an absolute path for a module's SourceDir; + // doing so will cause the value of `path.module` in Terraform configs to + // differ across plans and applies, since tfc-agent performs plans and + // applies in temporary directories. Instead, we try to resolve a relative + // path from Terraform's working directory, which should always be a + // reasonable SourceDir value. + workDir, err := os.Getwd() + if err != nil { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Cannot resolve working directory", + Detail: fmt.Sprintf("Failed to resolve current working directory: %s. This is a bug in Terraform - please report it.", err), + }) + } + relativeSourceDir, err := filepath.Rel(workDir, sourceDir) + if err != nil { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Cannot resolve relative path", + Detail: fmt.Sprintf("Failed to resolve relative path to module directory: %s. This is a bug in Terraform - please report it.", err), + }) + } + mod.SourceDir = relativeSourceDir return mod, diags } diff --git a/internal/stacks/stackruntime/internal/stackeval/planning_test.go b/internal/stacks/stackruntime/internal/stackeval/planning_test.go index e032d6f0fd..ac669fb7fc 100644 --- a/internal/stacks/stackruntime/internal/stackeval/planning_test.go +++ b/internal/stacks/stackruntime/internal/stackeval/planning_test.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "os" - "path/filepath" "strings" "testing" "time" @@ -852,17 +851,9 @@ func TestPlanning_PathValues(t *testing.T) { t.Fatalf("failed to get current working directory: %s", err) } - normalizePath := func(path string) string { - rel, err := filepath.Rel(cwd, path) - if err != nil { - t.Errorf("rel(%s,%s): %s", cwd, path, err) - return path - } - return rel - } - + // path.cwd should be absolute and all others should be relative, as per documentation. expected := map[string]string{ - "cwd": ".", + "cwd": cwd, "root": "testdata/sourcebundle/planning/path_values/module", // this is the root module of the component "module": "testdata/sourcebundle/planning/path_values/module", // this is the root module "child_root": "testdata/sourcebundle/planning/path_values/module", // should be the same for all modules @@ -870,11 +861,11 @@ func TestPlanning_PathValues(t *testing.T) { } actual := map[string]string{ - "cwd": normalizePath(component.PlannedOutputValues[addrs.OutputValue{Name: "cwd"}].AsString()), - "root": normalizePath(component.PlannedOutputValues[addrs.OutputValue{Name: "root"}].AsString()), - "module": normalizePath(component.PlannedOutputValues[addrs.OutputValue{Name: "module"}].AsString()), - "child_root": normalizePath(component.PlannedOutputValues[addrs.OutputValue{Name: "child_root"}].AsString()), - "child_module": normalizePath(component.PlannedOutputValues[addrs.OutputValue{Name: "child_module"}].AsString()), + "cwd": component.PlannedOutputValues[addrs.OutputValue{Name: "cwd"}].AsString(), + "root": component.PlannedOutputValues[addrs.OutputValue{Name: "root"}].AsString(), + "module": component.PlannedOutputValues[addrs.OutputValue{Name: "module"}].AsString(), + "child_root": component.PlannedOutputValues[addrs.OutputValue{Name: "child_root"}].AsString(), + "child_module": component.PlannedOutputValues[addrs.OutputValue{Name: "child_module"}].AsString(), } if cmp.Diff(expected, actual) != "" {