Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion internal/command/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,9 @@ func TestTest_Runs(t *testing.T) {
"ephemeral_output": {
code: 0,
},
"ephemeral_output_referenced": {
code: 0,
},
"no-tests": {
code: 0,
},
Expand Down Expand Up @@ -4001,7 +4004,6 @@ Error: Test assertion failed
on main.tftest.hcl line 8, in run "first":
8: condition = test_resource.resource.value == output.null_output
├────────────────
│ Warning: LHS and RHS values are of different types
│ Diff:
│ --- actual
│ +++ expected
Expand Down Expand Up @@ -4054,6 +4056,7 @@ Error: Unknown condition value
8: condition = output.destroy_fail == run.one.destroy_fail
├────────────────
│ output.destroy_fail is false
│ run.one.destroy_fail is a bool

Condition expression could not be evaluated at this time. This means you have
executed a %s block with %s and one of the values your
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
variable "foo" {
ephemeral = true
type = string
}
output "value" {
value = var.foo
ephemeral = true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
run "validate_ephemeral_output_plan" {
command = plan
variables {
foo = "whaaat"
}
assert {
condition = output.value == "whaaat"
error_message = "wrong value"
}
}
run "validate_ephemeral_output_apply" {
command = apply
variables {
foo = "whaaat"
}
assert {
condition = output.value == "whaaat"
error_message = "wrong value"
}
}
28 changes: 11 additions & 17 deletions internal/terraform/evaluate.go
Original file line number Diff line number Diff line change
Expand Up @@ -1115,27 +1115,21 @@ func (d *evaluationStateData) GetOutput(addr addrs.OutputValue, rng tfdiags.Sour
return cty.DynamicVal, diags
}

output := d.Evaluator.State.OutputValue(addr.Absolute(d.ModulePath))
if output == nil {
// Then the output itself returned null, so we'll package that up and
// pass it on.
output = &states.OutputValue{
Addr: addr.Absolute(d.ModulePath),
Value: cty.NilVal,
Sensitive: config.Sensitive,
}
} else if output.Value == cty.NilVal || output.Value.IsNull() {
// Then we did get a value but Terraform itself thought it was NilVal
// so we treat this as if the value isn't yet known.
output.Value = cty.DynamicVal
var value cty.Value
if !d.Evaluator.NamedValues.HasOutputValue(addr.Absolute(d.ModulePath)) {
value = cty.DynamicVal
} else {
value = d.Evaluator.NamedValues.GetOutputValue(addr.Absolute(d.ModulePath))
}

val := output.Value
if output.Sensitive {
val = val.Mark(marks.Sensitive)
if config.Sensitive {
value = value.Mark(marks.Sensitive)
}
if config.Ephemeral {
value = value.Mark(marks.Ephemeral)
}

return val, diags
return value, diags
}

// moduleDisplayAddr returns a string describing the given module instance
Expand Down
35 changes: 21 additions & 14 deletions internal/terraform/evaluate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ func TestEvaluatorGetPathAttr(t *testing.T) {
}

func TestEvaluatorGetOutputValue(t *testing.T) {
nvs := namedvals.NewState()
nvs.SetOutputValue(mustAbsOutputValue("output.some_output"), cty.StringVal("first"))
nvs.SetOutputValue(mustAbsOutputValue("output.some_other_output"), cty.StringVal("second"))
nvs.SetOutputValue(mustAbsOutputValue("output.some_third_output"), cty.StringVal("third").Mark(marks.Sensitive))

evaluator := &Evaluator{
Meta: &ContextMeta{
Env: "foo",
Expand All @@ -113,23 +118,13 @@ func TestEvaluatorGetOutputValue(t *testing.T) {
"some_other_output": {
Name: "some_other_output",
},
"some_third_output": {
Name: "some_third_output",
},
},
},
},
State: states.BuildState(func(state *states.SyncState) {
state.SetOutputValue(addrs.AbsOutputValue{
Module: addrs.RootModuleInstance,
OutputValue: addrs.OutputValue{
Name: "some_output",
},
}, cty.StringVal("first"), true)
state.SetOutputValue(addrs.AbsOutputValue{
Module: addrs.RootModuleInstance,
OutputValue: addrs.OutputValue{
Name: "some_other_output",
},
}, cty.StringVal("second"), false)
}).SyncWrapper(),
NamedValues: nvs,
}

data := &evaluationStateData{
Expand Down Expand Up @@ -162,6 +157,18 @@ func TestEvaluatorGetOutputValue(t *testing.T) {
if !got.RawEquals(want) {
t.Errorf("wrong result %#v; want %#v", got, want)
}

want = cty.StringVal("third").Mark(marks.Sensitive)
got, diags = scope.Data.GetOutput(addrs.OutputValue{
Name: "some_third_output",
}, tfdiags.SourceRange{})

if len(diags) != 0 {
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
}
if !got.RawEquals(want) {
t.Errorf("wrong result %#v; want %#v", got, want)
}
}

// This particularly tests that a sensitive attribute in config
Expand Down
20 changes: 10 additions & 10 deletions internal/terraform/node_output.go
Original file line number Diff line number Diff line change
Expand Up @@ -757,15 +757,6 @@ func (n *NodeApplyableOutput) setValue(namedVals *namedvals.State, state *states
changes.RemoveOutputChange(n.Addr)
}

// Null outputs must be saved for modules so that they can still be
// evaluated. Null root outputs are removed entirely, which is always fine
// because they can't be referenced by anything else in the configuration.
if n.Addr.Module.IsRoot() && val.IsNull() {
log.Printf("[TRACE] setValue: Removing %s from state (it is now null)", n.Addr)
state.RemoveOutputValue(n.Addr)
return
}

// caller leaves namedVals nil if they've already called this function
// with a different state, since we only have one namedVals regardless
// of how many states are involved in an operation.
Expand All @@ -779,6 +770,15 @@ func (n *NodeApplyableOutput) setValue(namedVals *namedvals.State, state *states
namedVals.SetOutputValue(n.Addr, saveVal)
}

// Null outputs must be saved for modules so that they can still be
// evaluated. Null root outputs are removed entirely, which is always fine
// because they can't be referenced by anything else in the configuration.
if n.Addr.Module.IsRoot() && val.IsNull() {
log.Printf("[TRACE] setValue: Removing %s from state (it is now null)", n.Addr)
state.RemoveOutputValue(n.Addr)
return
}

// Non-ephemeral output values get saved in the state too
if !n.Config.Ephemeral {
// The state itself doesn't represent unknown values, so we null them
Expand All @@ -798,6 +798,6 @@ func (n *NodeApplyableOutput) setValue(namedVals *namedvals.State, state *states
val = cty.UnknownAsNull(val)
}
}
state.SetOutputValue(n.Addr, val, n.Config.Sensitive)
}
state.SetOutputValue(n.Addr, val, n.Config.Sensitive)
}
Loading