mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
The "checks" package is an expansion what we previously called plans.Conditions to accommodate a new requirement that we be able to track which checks we're expecting to run even if we don't actually get around to running them, which will be helpful when we start using checks as part of our module testing story because test reporting tools appreciate there being a relatively consistent set of test cases from one run to the next. So far this should be essentially a no-op change from an external functionality standpoint, aside from some minor adjustments to how we report some of the error and warning cases from condition evaluation in light of the fact that the "checks" package can now track errors as a different outcome than a failure of a valid check. As is often the case with anything which changes what we track in the EvalContext and persist between plan and apply, Terraform Core is pretty brittle and so this had knock-on effects elsewhere too. Again, the goal is for these changes to not create any material externally-visible difference, and just to accommodate the new assumption that there will always be a "checks" object available for tracking during a graph walk.
188 lines
5.6 KiB
Go
188 lines
5.6 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/checks"
|
|
"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()
|
|
ctx.ChecksState = checks.NewState(nil)
|
|
|
|
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()
|
|
ctx.ChecksState = checks.NewState(nil)
|
|
|
|
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()
|
|
ctx.ChecksState = checks.NewState(nil)
|
|
|
|
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()
|
|
ctx.ChecksState = checks.NewState(nil)
|
|
|
|
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")
|
|
}
|
|
}
|