mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
If there are outputs in configuration, a destroy plan will always contain a "delete" change for each of these outputs. This leads to meaningless delete changes being present for outputs which were not present in state and therefore cannot be deleted. Since there is a change in the plan, this plan will then be considered applyable, and the user will be presented with text instructing them to apply a plan in which there are no actual changes. This commit stops the above from happening in the case of root module outputs.
183 lines
5.4 KiB
Go
183 lines
5.4 KiB
Go
package terraform
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/configs"
|
|
"github.com/hashicorp/terraform/internal/lang/marks"
|
|
"github.com/hashicorp/terraform/internal/states"
|
|
)
|
|
|
|
func TestNodeApplyableOutputExecute_knownValue(t *testing.T) {
|
|
ctx := new(MockEvalContext)
|
|
ctx.StateState = states.NewState().SyncWrapper()
|
|
ctx.RefreshStateState = states.NewState().SyncWrapper()
|
|
|
|
config := &configs.Output{Name: "map-output"}
|
|
addr := addrs.OutputValue{Name: config.Name}.Absolute(addrs.RootModuleInstance)
|
|
node := &NodeApplyableOutput{Config: config, Addr: addr}
|
|
val := cty.MapVal(map[string]cty.Value{
|
|
"a": cty.StringVal("b"),
|
|
})
|
|
ctx.EvaluateExprResult = val
|
|
|
|
err := node.Execute(ctx, walkApply)
|
|
if err != nil {
|
|
t.Fatalf("unexpected execute error: %s", err)
|
|
}
|
|
|
|
outputVal := ctx.StateState.OutputValue(addr)
|
|
if got, want := outputVal.Value, val; !got.RawEquals(want) {
|
|
t.Errorf("wrong output value in state\n got: %#v\nwant: %#v", got, want)
|
|
}
|
|
|
|
if !ctx.RefreshStateCalled {
|
|
t.Fatal("should have called RefreshState, but didn't")
|
|
}
|
|
refreshOutputVal := ctx.RefreshStateState.OutputValue(addr)
|
|
if got, want := refreshOutputVal.Value, val; !got.RawEquals(want) {
|
|
t.Fatalf("wrong output value in refresh state\n got: %#v\nwant: %#v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestNodeApplyableOutputExecute_noState(t *testing.T) {
|
|
ctx := new(MockEvalContext)
|
|
|
|
config := &configs.Output{Name: "map-output"}
|
|
addr := addrs.OutputValue{Name: config.Name}.Absolute(addrs.RootModuleInstance)
|
|
node := &NodeApplyableOutput{Config: config, Addr: addr}
|
|
val := cty.MapVal(map[string]cty.Value{
|
|
"a": cty.StringVal("b"),
|
|
})
|
|
ctx.EvaluateExprResult = val
|
|
|
|
err := node.Execute(ctx, walkApply)
|
|
if err != nil {
|
|
t.Fatalf("unexpected execute error: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestNodeApplyableOutputExecute_invalidDependsOn(t *testing.T) {
|
|
ctx := new(MockEvalContext)
|
|
ctx.StateState = states.NewState().SyncWrapper()
|
|
|
|
config := &configs.Output{
|
|
Name: "map-output",
|
|
DependsOn: []hcl.Traversal{
|
|
{
|
|
hcl.TraverseRoot{Name: "test_instance"},
|
|
hcl.TraverseAttr{Name: "foo"},
|
|
hcl.TraverseAttr{Name: "bar"},
|
|
},
|
|
},
|
|
}
|
|
addr := addrs.OutputValue{Name: config.Name}.Absolute(addrs.RootModuleInstance)
|
|
node := &NodeApplyableOutput{Config: config, Addr: addr}
|
|
val := cty.MapVal(map[string]cty.Value{
|
|
"a": cty.StringVal("b"),
|
|
})
|
|
ctx.EvaluateExprResult = val
|
|
|
|
diags := node.Execute(ctx, walkApply)
|
|
if !diags.HasErrors() {
|
|
t.Fatal("expected execute error, but there was none")
|
|
}
|
|
if got, want := diags.Err().Error(), "Invalid depends_on reference"; !strings.Contains(got, want) {
|
|
t.Errorf("expected error to include %q, but was: %s", want, got)
|
|
}
|
|
}
|
|
|
|
func TestNodeApplyableOutputExecute_sensitiveValueNotOutput(t *testing.T) {
|
|
ctx := new(MockEvalContext)
|
|
ctx.StateState = states.NewState().SyncWrapper()
|
|
|
|
config := &configs.Output{Name: "map-output"}
|
|
addr := addrs.OutputValue{Name: config.Name}.Absolute(addrs.RootModuleInstance)
|
|
node := &NodeApplyableOutput{Config: config, Addr: addr}
|
|
val := cty.MapVal(map[string]cty.Value{
|
|
"a": cty.StringVal("b").Mark(marks.Sensitive),
|
|
})
|
|
ctx.EvaluateExprResult = val
|
|
|
|
diags := node.Execute(ctx, walkApply)
|
|
if !diags.HasErrors() {
|
|
t.Fatal("expected execute error, but there was none")
|
|
}
|
|
if got, want := diags.Err().Error(), "Output refers to sensitive values"; !strings.Contains(got, want) {
|
|
t.Errorf("expected error to include %q, but was: %s", want, got)
|
|
}
|
|
}
|
|
|
|
func TestNodeApplyableOutputExecute_sensitiveValueAndOutput(t *testing.T) {
|
|
ctx := new(MockEvalContext)
|
|
ctx.StateState = states.NewState().SyncWrapper()
|
|
|
|
config := &configs.Output{
|
|
Name: "map-output",
|
|
Sensitive: true,
|
|
}
|
|
addr := addrs.OutputValue{Name: config.Name}.Absolute(addrs.RootModuleInstance)
|
|
node := &NodeApplyableOutput{Config: config, Addr: addr}
|
|
val := cty.MapVal(map[string]cty.Value{
|
|
"a": cty.StringVal("b").Mark(marks.Sensitive),
|
|
})
|
|
ctx.EvaluateExprResult = val
|
|
|
|
err := node.Execute(ctx, walkApply)
|
|
if err != nil {
|
|
t.Fatalf("unexpected execute error: %s", err)
|
|
}
|
|
|
|
// Unmarked value should be stored in state
|
|
outputVal := ctx.StateState.OutputValue(addr)
|
|
want, _ := val.UnmarkDeep()
|
|
if got := outputVal.Value; !got.RawEquals(want) {
|
|
t.Errorf("wrong output value in state\n got: %#v\nwant: %#v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestNodeDestroyableOutputExecute(t *testing.T) {
|
|
outputAddr := addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)
|
|
|
|
state := states.NewState()
|
|
state.Module(addrs.RootModuleInstance).SetOutputValue("foo", cty.StringVal("bar"), false)
|
|
state.OutputValue(outputAddr)
|
|
|
|
ctx := &MockEvalContext{
|
|
StateState: state.SyncWrapper(),
|
|
}
|
|
node := NodeDestroyableOutput{Addr: outputAddr}
|
|
|
|
diags := node.Execute(ctx, walkApply)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("Unexpected error: %s", diags.Err())
|
|
}
|
|
if state.OutputValue(outputAddr) != nil {
|
|
t.Fatal("Unexpected outputs in state after removal")
|
|
}
|
|
}
|
|
|
|
func TestNodeDestroyableOutputExecute_notInState(t *testing.T) {
|
|
outputAddr := addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)
|
|
|
|
state := states.NewState()
|
|
|
|
ctx := &MockEvalContext{
|
|
StateState: state.SyncWrapper(),
|
|
}
|
|
node := NodeDestroyableOutput{Addr: outputAddr}
|
|
|
|
diags := node.Execute(ctx, walkApply)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("Unexpected error: %s", diags.Err())
|
|
}
|
|
if state.OutputValue(outputAddr) != nil {
|
|
t.Fatal("Unexpected outputs in state after removal")
|
|
}
|
|
}
|