opentofu/terraform/reduce_plan_test.go
Kristin Laemmert e7aaf9e39f
Eval() Refactor: Plan Edition (#27177)
* terraforn: refactor EvalRefresh

EvalRefresh.Eval(ctx) is now Refresh(evalRefreshReqest, ctx). While none
of the inner logic of the function has changed, it now returns a
states.ResourceInstanceObject instead of updating a pointer. This is a
human-centric change, meant to make the logic flow (in the calling
functions) easier to follow.

* terraform: refactor EvalReadDataPlan and Apply

This is a very minor refactor that removes the (currently) redundant
types EvalReadDataPlan and EvalReadDataApply in favor of using
EvalReadData with a Plan and Apply functions.

This is in effect an aesthetic change; since there is no longer an
Eval() abstraction we can rename functions to make their functionality
as obvious as possible.

* terraform: refactor EvalCheckPlannedChange

EvalCheckPlannedChange was only used by NodeApplyableResourceInstance
and has been refactored into a method on that type called
checkPlannedChange.

* terraform: refactor EvalDiff.Eval

EvalDiff.Eval is now a method on NodeResourceAbstracted called Plan
which takes as a parameter an EvalPlanRequest. Instead of updating
pointers it returns a new plan and state.

I removed as many redundant fields from the original EvalDiff struct as
possible.

* terraform: refactor EvalReduceDiff

EvalReduceDiff is now reducePlan, a regular function (without a method)
that returns a value.

* terraform: refactor EvalDiffDestroy

EvalDiffDestroy.Eval is now NodeAbstractResourceInstance.PlanDestroy
which takes ctx, state and optional DeposedKey and returns a change.
I've removed the state return value since it was only ever returning a
nil state.

* terraform: refactor EvalWriteDiff

EvalWriteDiff.Eval is now NodeAbstractResourceInstance.WriteChange.

* rename files to something more logical

* terrafrom: refresh refactor, continued!

I had originally made Refresh a stand-alone function since it was
(obnoxiously) called from a graphNodeImportStateSub, but after some
(greatly appreciated) prompting in the PR I instead made it a method on
the NodeAbstractResourceInstance, in keeping with the other refactored
eval nodes, and then built a NodeAbstractResourceInstance inside import.

Since I did that I could also remove my duplicated 'writeState' code
inside graphNodeImportStateSub and use n.writeResourceInstanceState, so
double thanks!

* unexport eval methods

* re-refactor Plan, it made more sense on NodeAbstractResourceInstance. Sorry

* Remove uninformative `Eval`s from EvalReadData, consolidate to a single
file, and rename file to match function names.

* manual rebase
2020-12-08 08:50:30 -05:00

396 lines
11 KiB
Go

package terraform
import (
"testing"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/zclconf/go-cty/cty"
)
func TestProcessIgnoreChangesIndividual(t *testing.T) {
tests := map[string]struct {
Old, New cty.Value
Ignore []string
Want cty.Value
}{
"string": {
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("a value"),
"b": cty.StringVal("b value"),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("new a value"),
"b": cty.StringVal("new b value"),
}),
[]string{"a"},
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("a value"),
"b": cty.StringVal("new b value"),
}),
},
"changed type": {
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("a value"),
"b": cty.StringVal("b value"),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.NumberIntVal(1),
"b": cty.StringVal("new b value"),
}),
[]string{"a"},
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("a value"),
"b": cty.StringVal("new b value"),
}),
},
"list": {
cty.ObjectVal(map[string]cty.Value{
"a": cty.ListVal([]cty.Value{
cty.StringVal("a0 value"),
cty.StringVal("a1 value"),
}),
"b": cty.StringVal("b value"),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.ListVal([]cty.Value{
cty.StringVal("new a0 value"),
cty.StringVal("new a1 value"),
}),
"b": cty.StringVal("new b value"),
}),
[]string{"a"},
cty.ObjectVal(map[string]cty.Value{
"a": cty.ListVal([]cty.Value{
cty.StringVal("a0 value"),
cty.StringVal("a1 value"),
}),
"b": cty.StringVal("new b value"),
}),
},
"list_index": {
cty.ObjectVal(map[string]cty.Value{
"a": cty.ListVal([]cty.Value{
cty.StringVal("a0 value"),
cty.StringVal("a1 value"),
}),
"b": cty.StringVal("b value"),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.ListVal([]cty.Value{
cty.StringVal("new a0 value"),
cty.StringVal("new a1 value"),
}),
"b": cty.StringVal("new b value"),
}),
[]string{"a[1]"},
cty.ObjectVal(map[string]cty.Value{
"a": cty.ListVal([]cty.Value{
cty.StringVal("new a0 value"),
cty.StringVal("a1 value"),
}),
"b": cty.StringVal("new b value"),
}),
},
"map": {
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a0": cty.StringVal("a0 value"),
"a1": cty.StringVal("a1 value"),
}),
"b": cty.StringVal("b value"),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a0": cty.StringVal("new a0 value"),
"a1": cty.UnknownVal(cty.String),
}),
"b": cty.StringVal("b value"),
}),
[]string{`a`},
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a0": cty.StringVal("a0 value"),
"a1": cty.StringVal("a1 value"),
}),
"b": cty.StringVal("b value"),
}),
},
"map_index": {
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a0": cty.StringVal("a0 value"),
"a1": cty.StringVal("a1 value"),
}),
"b": cty.StringVal("b value"),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a0": cty.StringVal("new a0 value"),
"a1": cty.StringVal("new a1 value"),
}),
"b": cty.StringVal("b value"),
}),
[]string{`a["a1"]`},
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a0": cty.StringVal("new a0 value"),
"a1": cty.StringVal("a1 value"),
}),
"b": cty.StringVal("b value"),
}),
},
"map_index_no_config": {
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a0": cty.StringVal("a0 value"),
"a1": cty.StringVal("a1 value"),
}),
"b": cty.StringVal("b value"),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.NullVal(cty.Map(cty.String)),
"b": cty.StringVal("b value"),
}),
[]string{`a["a1"]`},
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a1": cty.StringVal("a1 value"),
}),
"b": cty.StringVal("b value"),
}),
},
"map_index_unknown_value": {
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a0": cty.StringVal("a0 value"),
"a1": cty.StringVal("a1 value"),
}),
"b": cty.StringVal("b value"),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a0": cty.StringVal("a0 value"),
"a1": cty.UnknownVal(cty.String),
}),
"b": cty.StringVal("b value"),
}),
[]string{`a["a1"]`},
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a0": cty.StringVal("a0 value"),
"a1": cty.StringVal("a1 value"),
}),
"b": cty.StringVal("b value"),
}),
},
"map_index_multiple_keys": {
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a0": cty.StringVal("a0 value"),
"a1": cty.StringVal("a1 value"),
"a2": cty.StringVal("a2 value"),
"a3": cty.StringVal("a3 value"),
}),
"b": cty.StringVal("b value"),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.NullVal(cty.Map(cty.String)),
"b": cty.StringVal("new b value"),
}),
[]string{`a["a1"]`, `a["a2"]`, `a["a3"]`, `b`},
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a1": cty.StringVal("a1 value"),
"a2": cty.StringVal("a2 value"),
"a3": cty.StringVal("a3 value"),
}),
"b": cty.StringVal("b value"),
}),
},
"map_index_redundant": {
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a0": cty.StringVal("a0 value"),
"a1": cty.StringVal("a1 value"),
"a2": cty.StringVal("a2 value"),
}),
"b": cty.StringVal("b value"),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.NullVal(cty.Map(cty.String)),
"b": cty.StringVal("new b value"),
}),
[]string{`a["a1"]`, `a`, `b`},
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a0": cty.StringVal("a0 value"),
"a1": cty.StringVal("a1 value"),
"a2": cty.StringVal("a2 value"),
}),
"b": cty.StringVal("b value"),
}),
},
"missing_map_index": {
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a0": cty.StringVal("a0 value"),
"a1": cty.StringVal("a1 value"),
}),
"b": cty.StringVal("b value"),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapValEmpty(cty.String),
"b": cty.StringVal("b value"),
}),
[]string{`a["a1"]`},
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a1": cty.StringVal("a1 value"),
}),
"b": cty.StringVal("b value"),
}),
},
"missing_map_index_empty": {
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapValEmpty(cty.String),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a": cty.StringVal("a0 value"),
}),
}),
[]string{`a["a"]`},
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapValEmpty(cty.String),
}),
},
"missing_map_index_to_object": {
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a": cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("aa0"),
"b": cty.StringVal("ab0"),
}),
"b": cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("ba0"),
"b": cty.StringVal("bb0"),
}),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapValEmpty(
cty.Object(map[string]cty.Type{
"a": cty.String,
"b": cty.String,
}),
),
}),
// we expect the config to be used here, as the ignore changes was
// `a["a"].b`, but the change was larger than that removing
// `a["a"]` entirely.
[]string{`a["a"].b`},
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapValEmpty(
cty.Object(map[string]cty.Type{
"a": cty.String,
"b": cty.String,
}),
),
}),
},
"missing_prior_map_index": {
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a0": cty.StringVal("a0 value"),
}),
"b": cty.StringVal("b value"),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a0": cty.StringVal("a0 value"),
"a1": cty.StringVal("new a1 value"),
}),
"b": cty.StringVal("b value"),
}),
[]string{`a["a1"]`},
cty.ObjectVal(map[string]cty.Value{
"a": cty.MapVal(map[string]cty.Value{
"a0": cty.StringVal("a0 value"),
}),
"b": cty.StringVal("b value"),
}),
},
"object attribute": {
cty.ObjectVal(map[string]cty.Value{
"a": cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("a.foo value"),
"bar": cty.StringVal("a.bar value"),
}),
"b": cty.StringVal("b value"),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("new a.foo value"),
"bar": cty.StringVal("new a.bar value"),
}),
"b": cty.StringVal("new b value"),
}),
[]string{"a.bar"},
cty.ObjectVal(map[string]cty.Value{
"a": cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("new a.foo value"),
"bar": cty.StringVal("a.bar value"),
}),
"b": cty.StringVal("new b value"),
}),
},
"unknown_object_attribute": {
cty.ObjectVal(map[string]cty.Value{
"a": cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("a.foo value"),
"bar": cty.StringVal("a.bar value"),
}),
"b": cty.StringVal("b value"),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("new a.foo value"),
"bar": cty.UnknownVal(cty.String),
}),
"b": cty.StringVal("new b value"),
}),
[]string{"a.bar"},
cty.ObjectVal(map[string]cty.Value{
"a": cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("new a.foo value"),
"bar": cty.StringVal("a.bar value"),
}),
"b": cty.StringVal("new b value"),
}),
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
ignore := make([]hcl.Traversal, len(test.Ignore))
for i, ignoreStr := range test.Ignore {
trav, diags := hclsyntax.ParseTraversalAbs([]byte(ignoreStr), "", hcl.Pos{Line: 1, Column: 1})
if diags.HasErrors() {
t.Fatalf("failed to parse %q: %s", ignoreStr, diags.Error())
}
ignore[i] = trav
}
ret, diags := processIgnoreChangesIndividual(test.Old, test.New, ignore)
if diags.HasErrors() {
t.Fatal(diags.Err())
}
if got, want := ret, test.Want; !want.RawEquals(got) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want)
}
})
}
}