Skip to content

Commit e44b870

Browse files
authored
don't write ephemeral outputs to state (#37821)
1 parent 7ac2b79 commit e44b870

File tree

6 files changed

+74
-42
lines changed

6 files changed

+74
-42
lines changed

internal/command/test_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,9 @@ func TestTest_Runs(t *testing.T) {
412412
"ephemeral_output": {
413413
code: 0,
414414
},
415+
"ephemeral_output_referenced": {
416+
code: 0,
417+
},
415418
"no-tests": {
416419
code: 0,
417420
},
@@ -4001,7 +4004,6 @@ Error: Test assertion failed
40014004
on main.tftest.hcl line 8, in run "first":
40024005
8: condition = test_resource.resource.value == output.null_output
40034006
├────────────────
4004-
│ Warning: LHS and RHS values are of different types
40054007
│ Diff:
40064008
│ --- actual
40074009
│ +++ expected
@@ -4054,6 +4056,7 @@ Error: Unknown condition value
40544056
8: condition = output.destroy_fail == run.one.destroy_fail
40554057
├────────────────
40564058
│ output.destroy_fail is false
4059+
│ run.one.destroy_fail is a bool
40574060
40584061
Condition expression could not be evaluated at this time. This means you have
40594062
executed a %s block with %s and one of the values your
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
variable "foo" {
2+
ephemeral = true
3+
type = string
4+
}
5+
output "value" {
6+
value = var.foo
7+
ephemeral = true
8+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
run "validate_ephemeral_output_plan" {
2+
command = plan
3+
variables {
4+
foo = "whaaat"
5+
}
6+
assert {
7+
condition = output.value == "whaaat"
8+
error_message = "wrong value"
9+
}
10+
}
11+
run "validate_ephemeral_output_apply" {
12+
command = apply
13+
variables {
14+
foo = "whaaat"
15+
}
16+
assert {
17+
condition = output.value == "whaaat"
18+
error_message = "wrong value"
19+
}
20+
}

internal/terraform/evaluate.go

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,27 +1115,21 @@ func (d *evaluationStateData) GetOutput(addr addrs.OutputValue, rng tfdiags.Sour
11151115
return cty.DynamicVal, diags
11161116
}
11171117

1118-
output := d.Evaluator.State.OutputValue(addr.Absolute(d.ModulePath))
1119-
if output == nil {
1120-
// Then the output itself returned null, so we'll package that up and
1121-
// pass it on.
1122-
output = &states.OutputValue{
1123-
Addr: addr.Absolute(d.ModulePath),
1124-
Value: cty.NilVal,
1125-
Sensitive: config.Sensitive,
1126-
}
1127-
} else if output.Value == cty.NilVal || output.Value.IsNull() {
1128-
// Then we did get a value but Terraform itself thought it was NilVal
1129-
// so we treat this as if the value isn't yet known.
1130-
output.Value = cty.DynamicVal
1118+
var value cty.Value
1119+
if !d.Evaluator.NamedValues.HasOutputValue(addr.Absolute(d.ModulePath)) {
1120+
value = cty.DynamicVal
1121+
} else {
1122+
value = d.Evaluator.NamedValues.GetOutputValue(addr.Absolute(d.ModulePath))
11311123
}
11321124

1133-
val := output.Value
1134-
if output.Sensitive {
1135-
val = val.Mark(marks.Sensitive)
1125+
if config.Sensitive {
1126+
value = value.Mark(marks.Sensitive)
1127+
}
1128+
if config.Ephemeral {
1129+
value = value.Mark(marks.Ephemeral)
11361130
}
11371131

1138-
return val, diags
1132+
return value, diags
11391133
}
11401134

11411135
// moduleDisplayAddr returns a string describing the given module instance

internal/terraform/evaluate_test.go

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ func TestEvaluatorGetPathAttr(t *testing.T) {
9999
}
100100

101101
func TestEvaluatorGetOutputValue(t *testing.T) {
102+
nvs := namedvals.NewState()
103+
nvs.SetOutputValue(mustAbsOutputValue("output.some_output"), cty.StringVal("first"))
104+
nvs.SetOutputValue(mustAbsOutputValue("output.some_other_output"), cty.StringVal("second"))
105+
nvs.SetOutputValue(mustAbsOutputValue("output.some_third_output"), cty.StringVal("third").Mark(marks.Sensitive))
106+
102107
evaluator := &Evaluator{
103108
Meta: &ContextMeta{
104109
Env: "foo",
@@ -113,23 +118,13 @@ func TestEvaluatorGetOutputValue(t *testing.T) {
113118
"some_other_output": {
114119
Name: "some_other_output",
115120
},
121+
"some_third_output": {
122+
Name: "some_third_output",
123+
},
116124
},
117125
},
118126
},
119-
State: states.BuildState(func(state *states.SyncState) {
120-
state.SetOutputValue(addrs.AbsOutputValue{
121-
Module: addrs.RootModuleInstance,
122-
OutputValue: addrs.OutputValue{
123-
Name: "some_output",
124-
},
125-
}, cty.StringVal("first"), true)
126-
state.SetOutputValue(addrs.AbsOutputValue{
127-
Module: addrs.RootModuleInstance,
128-
OutputValue: addrs.OutputValue{
129-
Name: "some_other_output",
130-
},
131-
}, cty.StringVal("second"), false)
132-
}).SyncWrapper(),
127+
NamedValues: nvs,
133128
}
134129

135130
data := &evaluationStateData{
@@ -162,6 +157,18 @@ func TestEvaluatorGetOutputValue(t *testing.T) {
162157
if !got.RawEquals(want) {
163158
t.Errorf("wrong result %#v; want %#v", got, want)
164159
}
160+
161+
want = cty.StringVal("third").Mark(marks.Sensitive)
162+
got, diags = scope.Data.GetOutput(addrs.OutputValue{
163+
Name: "some_third_output",
164+
}, tfdiags.SourceRange{})
165+
166+
if len(diags) != 0 {
167+
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
168+
}
169+
if !got.RawEquals(want) {
170+
t.Errorf("wrong result %#v; want %#v", got, want)
171+
}
165172
}
166173

167174
// This particularly tests that a sensitive attribute in config

internal/terraform/node_output.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -757,15 +757,6 @@ func (n *NodeApplyableOutput) setValue(namedVals *namedvals.State, state *states
757757
changes.RemoveOutputChange(n.Addr)
758758
}
759759

760-
// Null outputs must be saved for modules so that they can still be
761-
// evaluated. Null root outputs are removed entirely, which is always fine
762-
// because they can't be referenced by anything else in the configuration.
763-
if n.Addr.Module.IsRoot() && val.IsNull() {
764-
log.Printf("[TRACE] setValue: Removing %s from state (it is now null)", n.Addr)
765-
state.RemoveOutputValue(n.Addr)
766-
return
767-
}
768-
769760
// caller leaves namedVals nil if they've already called this function
770761
// with a different state, since we only have one namedVals regardless
771762
// of how many states are involved in an operation.
@@ -779,6 +770,15 @@ func (n *NodeApplyableOutput) setValue(namedVals *namedvals.State, state *states
779770
namedVals.SetOutputValue(n.Addr, saveVal)
780771
}
781772

773+
// Null outputs must be saved for modules so that they can still be
774+
// evaluated. Null root outputs are removed entirely, which is always fine
775+
// because they can't be referenced by anything else in the configuration.
776+
if n.Addr.Module.IsRoot() && val.IsNull() {
777+
log.Printf("[TRACE] setValue: Removing %s from state (it is now null)", n.Addr)
778+
state.RemoveOutputValue(n.Addr)
779+
return
780+
}
781+
782782
// Non-ephemeral output values get saved in the state too
783783
if !n.Config.Ephemeral {
784784
// The state itself doesn't represent unknown values, so we null them
@@ -798,6 +798,6 @@ func (n *NodeApplyableOutput) setValue(namedVals *namedvals.State, state *states
798798
val = cty.UnknownAsNull(val)
799799
}
800800
}
801+
state.SetOutputValue(n.Addr, val, n.Config.Sensitive)
801802
}
802-
state.SetOutputValue(n.Addr, val, n.Config.Sensitive)
803803
}

0 commit comments

Comments
 (0)