Skip to content

Commit 871d0a2

Browse files
committed
Mark templatefile as impure to defer evaluation when it contains impure function calls
1 parent 620264e commit 871d0a2

5 files changed

Lines changed: 79 additions & 0 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
kind: BUG FIXES
2+
body: |-
3+
core: Fixed `templatefile` function returning "inconsistent result" error when templates contain impure function calls like `timestamp()`
4+
time: 2025-10-24T13:18:00.000000-07:00
5+
custom:
6+
Issue: "37807"

internal/lang/functions.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919

2020
var impureFunctions = []string{
2121
"bcrypt",
22+
"templatefile",
2223
"timestamp",
2324
"uuid",
2425
}

internal/terraform/context_functions_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,68 @@ func TestContext2Plan_filesystemFunctionImpureApply(t *testing.T) {
292292
}
293293
}
294294

295+
// TestContext2Plan_templatefileWithImpureFunctions verifies that templatefile
296+
// works correctly when templates contain impure function calls like timestamp()
297+
// by marking templatefile as impure to defer evaluation until apply
298+
func TestContext2Plan_templatefileWithImpureFunctions(t *testing.T) {
299+
m, snap := testModuleWithSnapshot(t, "templatefile-timestamp")
300+
301+
p := testProvider("test")
302+
303+
ctx := testContext2(t, &ContextOpts{
304+
Providers: map[addrs.Provider]providers.Factory{
305+
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
306+
},
307+
})
308+
309+
// Plan should succeed with templatefile result showing as unknown
310+
plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
311+
tfdiags.AssertNoErrors(t, diags)
312+
313+
// The output should be unknown during plan since templatefile is now marked as impure
314+
outChangeSrc := plan.Changes.OutputValue(addrs.RootModuleInstance.OutputValue("result"))
315+
if outChangeSrc == nil {
316+
t.Fatal("expected output change to exist")
317+
}
318+
outChange, err := outChangeSrc.Decode()
319+
if err != nil {
320+
t.Fatalf("failed to decode output change: %s", err)
321+
}
322+
if outChange.After.IsKnown() {
323+
t.Fatalf("expected templatefile result to be unknown during plan, got: %#v", outChange.After)
324+
}
325+
326+
// Write / Read plan to simulate running it through a Plan file
327+
ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan)
328+
if err != nil {
329+
t.Fatalf("failed to round-trip through planfile: %s", err)
330+
}
331+
332+
ctxOpts.Providers = map[addrs.Provider]providers.Factory{
333+
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
334+
}
335+
ctx = testContext2(t, ctxOpts)
336+
337+
// Apply should succeed - no "inconsistent result" error even though
338+
// timestamp() will return different values
339+
state, diags := ctx.Apply(plan, m, nil)
340+
tfdiags.AssertNoErrors(t, diags)
341+
342+
// The output should now be known and contain a timestamp
343+
outputVal := state.OutputValue(addrs.RootModuleInstance.OutputValue("result"))
344+
if outputVal == nil {
345+
t.Fatal("expected output to exist")
346+
}
347+
if !outputVal.Value.IsKnown() {
348+
t.Fatal("expected output to be known after apply")
349+
}
350+
351+
result := outputVal.Value.AsString()
352+
if !strings.Contains(result, "Generated at:") {
353+
t.Fatalf("expected output to contain timestamp, got: %s", result)
354+
}
355+
}
356+
295357
func TestContext2Validate_providerFunctionDiagnostics(t *testing.T) {
296358
provider := &testing_provider.MockProvider{
297359
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
locals {
2+
template_result = templatefile("${path.module}/template.tftpl", { name = "terraform" })
3+
}
4+
5+
output "result" {
6+
value = local.template_result
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Hello ${name}!
2+
Generated at: ${timestamp()}
3+
UUID: ${uuid()}

0 commit comments

Comments
 (0)