mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Add support for sets in the structured renderer (#32409)
* prep for processing the structured run output * undo unwanted change to a json key * Add skeleton functions and API for refactored renderer * goimports * Fix documentation of the RenderOpts struct * Add rendering functionality for primitives to the structured renderer * add test case for override * Add support for parsing and rendering sensitive values in the renderer * Add support for unknown/computed values in the structured renderer * delete missing unit tests * Add support for object attributes in the structured renderer * goimports * Add support for the replace paths data in the structured renderer * Add support for maps in the structured renderer * Add support for lists in the structured renderer * goimports * Add support for sets in the structured renderer * goimports
This commit is contained in:
parent
aff94591c1
commit
69cce3597f
@ -24,9 +24,6 @@ func NestedList(elements []Change) Renderer {
|
||||
type listRenderer struct {
|
||||
NoWarningsRenderer
|
||||
|
||||
// displayContext tells the renderer to display additional information about
|
||||
// the before and after index values within a given list. For example, index
|
||||
//
|
||||
displayContext bool
|
||||
elements []Change
|
||||
}
|
||||
|
@ -889,6 +889,272 @@ func TestRenderers(t *testing.T) {
|
||||
[
|
||||
~ 0 -> (known after apply),
|
||||
]
|
||||
`,
|
||||
},
|
||||
"set_create_empty": {
|
||||
change: Change{
|
||||
renderer: Set([]Change{}),
|
||||
action: plans.Create,
|
||||
},
|
||||
expected: "[]",
|
||||
},
|
||||
"set_create": {
|
||||
change: Change{
|
||||
renderer: Set([]Change{
|
||||
{
|
||||
renderer: Primitive(nil, strptr("1")),
|
||||
action: plans.Create,
|
||||
},
|
||||
}),
|
||||
action: plans.Create,
|
||||
},
|
||||
expected: `
|
||||
[
|
||||
+ 1,
|
||||
]
|
||||
`,
|
||||
},
|
||||
"set_delete_empty": {
|
||||
change: Change{
|
||||
renderer: Set([]Change{}),
|
||||
action: plans.Delete,
|
||||
},
|
||||
expected: "[] -> null",
|
||||
},
|
||||
"set_delete": {
|
||||
change: Change{
|
||||
renderer: Set([]Change{
|
||||
{
|
||||
renderer: Primitive(strptr("1"), nil),
|
||||
action: plans.Delete,
|
||||
},
|
||||
}),
|
||||
action: plans.Delete,
|
||||
},
|
||||
expected: `
|
||||
[
|
||||
- 1,
|
||||
] -> null
|
||||
`,
|
||||
},
|
||||
"set_create_element": {
|
||||
change: Change{
|
||||
renderer: Set([]Change{
|
||||
{
|
||||
renderer: Primitive(nil, strptr("1")),
|
||||
action: plans.Create,
|
||||
},
|
||||
}),
|
||||
action: plans.Update,
|
||||
},
|
||||
expected: `
|
||||
[
|
||||
+ 1,
|
||||
]
|
||||
`,
|
||||
},
|
||||
"set_update_element": {
|
||||
change: Change{
|
||||
renderer: Set([]Change{
|
||||
{
|
||||
renderer: Primitive(strptr("0"), strptr("1")),
|
||||
action: plans.Update,
|
||||
},
|
||||
}),
|
||||
action: plans.Update,
|
||||
},
|
||||
expected: `
|
||||
[
|
||||
~ 0 -> 1,
|
||||
]
|
||||
`,
|
||||
},
|
||||
"set_replace_element": {
|
||||
change: Change{
|
||||
renderer: Set([]Change{
|
||||
{
|
||||
renderer: Primitive(strptr("0"), nil),
|
||||
action: plans.Delete,
|
||||
},
|
||||
{
|
||||
renderer: Primitive(nil, strptr("1")),
|
||||
action: plans.Create,
|
||||
},
|
||||
}),
|
||||
action: plans.Update,
|
||||
},
|
||||
expected: `
|
||||
[
|
||||
- 0,
|
||||
+ 1,
|
||||
]
|
||||
`,
|
||||
},
|
||||
"set_delete_element": {
|
||||
change: Change{
|
||||
renderer: Set([]Change{
|
||||
{
|
||||
renderer: Primitive(strptr("0"), nil),
|
||||
action: plans.Delete,
|
||||
},
|
||||
}),
|
||||
action: plans.Update,
|
||||
},
|
||||
expected: `
|
||||
[
|
||||
- 0,
|
||||
]
|
||||
`,
|
||||
},
|
||||
"set_update_forces_replacement": {
|
||||
change: Change{
|
||||
renderer: Set([]Change{
|
||||
{
|
||||
renderer: Primitive(strptr("0"), strptr("1")),
|
||||
action: plans.Update,
|
||||
},
|
||||
}),
|
||||
action: plans.Update,
|
||||
replace: true,
|
||||
},
|
||||
expected: `
|
||||
[ # forces replacement
|
||||
~ 0 -> 1,
|
||||
]
|
||||
`,
|
||||
},
|
||||
"set_update_ignores_unchanged": {
|
||||
change: Change{
|
||||
renderer: Set([]Change{
|
||||
{
|
||||
renderer: Primitive(strptr("0"), strptr("0")),
|
||||
action: plans.NoOp,
|
||||
},
|
||||
{
|
||||
renderer: Primitive(strptr("1"), strptr("1")),
|
||||
action: plans.NoOp,
|
||||
},
|
||||
{
|
||||
renderer: Primitive(strptr("2"), strptr("5")),
|
||||
action: plans.Update,
|
||||
},
|
||||
{
|
||||
renderer: Primitive(strptr("3"), strptr("3")),
|
||||
action: plans.NoOp,
|
||||
},
|
||||
{
|
||||
renderer: Primitive(strptr("4"), strptr("4")),
|
||||
action: plans.NoOp,
|
||||
},
|
||||
}),
|
||||
action: plans.Update,
|
||||
},
|
||||
expected: `
|
||||
[
|
||||
~ 2 -> 5,
|
||||
# (4 unchanged elements hidden)
|
||||
]
|
||||
`,
|
||||
},
|
||||
"set_create_sensitive_element": {
|
||||
change: Change{
|
||||
renderer: Set([]Change{
|
||||
{
|
||||
renderer: Sensitive(nil, 1, false, true),
|
||||
action: plans.Create,
|
||||
},
|
||||
}),
|
||||
action: plans.Update,
|
||||
},
|
||||
expected: `
|
||||
[
|
||||
+ (sensitive),
|
||||
]
|
||||
`,
|
||||
},
|
||||
"set_delete_sensitive_element": {
|
||||
change: Change{
|
||||
renderer: Set([]Change{
|
||||
{
|
||||
renderer: Sensitive(1, nil, true, false),
|
||||
action: plans.Delete,
|
||||
},
|
||||
}),
|
||||
action: plans.Update,
|
||||
},
|
||||
expected: `
|
||||
[
|
||||
- (sensitive),
|
||||
]
|
||||
`,
|
||||
},
|
||||
"set_update_sensitive_element": {
|
||||
change: Change{
|
||||
renderer: Set([]Change{
|
||||
{
|
||||
renderer: Sensitive(nil, 1, false, true),
|
||||
action: plans.Update,
|
||||
},
|
||||
}),
|
||||
action: plans.Update,
|
||||
},
|
||||
expected: `
|
||||
[
|
||||
~ (sensitive),
|
||||
]
|
||||
`,
|
||||
},
|
||||
"set_update_sensitive_element_status": {
|
||||
change: Change{
|
||||
renderer: Set([]Change{
|
||||
{
|
||||
renderer: Sensitive(1, 2, false, true),
|
||||
action: plans.Update,
|
||||
},
|
||||
}),
|
||||
action: plans.Update,
|
||||
},
|
||||
expected: `
|
||||
[
|
||||
# Warning: this attribute value will be marked as sensitive and will not
|
||||
# display in UI output after applying this change.
|
||||
~ (sensitive),
|
||||
]
|
||||
`,
|
||||
},
|
||||
"set_create_computed_element": {
|
||||
change: Change{
|
||||
renderer: Set([]Change{
|
||||
{
|
||||
renderer: Computed(Change{}),
|
||||
action: plans.Create,
|
||||
},
|
||||
}),
|
||||
action: plans.Update,
|
||||
},
|
||||
expected: `
|
||||
[
|
||||
+ (known after apply),
|
||||
]
|
||||
`,
|
||||
},
|
||||
"set_update_computed_element": {
|
||||
change: Change{
|
||||
renderer: Set([]Change{
|
||||
{
|
||||
renderer: Computed(Change{
|
||||
renderer: Primitive(strptr("0"), nil),
|
||||
action: plans.Delete,
|
||||
}),
|
||||
action: plans.Update,
|
||||
},
|
||||
}),
|
||||
action: plans.Update,
|
||||
},
|
||||
expected: `
|
||||
[
|
||||
~ 0 -> (known after apply),
|
||||
]
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
53
internal/command/jsonformat/change/set.go
Normal file
53
internal/command/jsonformat/change/set.go
Normal file
@ -0,0 +1,53 @@
|
||||
package change
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/command/format"
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
)
|
||||
|
||||
func Set(elements []Change) Renderer {
|
||||
return &setRenderer{
|
||||
elements: elements,
|
||||
}
|
||||
}
|
||||
|
||||
type setRenderer struct {
|
||||
NoWarningsRenderer
|
||||
|
||||
elements []Change
|
||||
}
|
||||
|
||||
func (renderer setRenderer) Render(change Change, indent int, opts RenderOpts) string {
|
||||
if len(renderer.elements) == 0 {
|
||||
return fmt.Sprintf("[]%s%s", change.nullSuffix(opts.overrideNullSuffix), change.forcesReplacement())
|
||||
}
|
||||
|
||||
elementOpts := opts.Clone()
|
||||
elementOpts.overrideNullSuffix = true
|
||||
|
||||
unchangedElements := 0
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(fmt.Sprintf("[%s\n", change.forcesReplacement()))
|
||||
for _, element := range renderer.elements {
|
||||
if element.action == plans.NoOp && !opts.showUnchangedChildren {
|
||||
unchangedElements++
|
||||
continue
|
||||
}
|
||||
|
||||
for _, warning := range element.Warnings(indent + 1) {
|
||||
buf.WriteString(fmt.Sprintf("%s%s\n", change.indent(indent+1), warning))
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf("%s%s %s,\n", change.indent(indent+1), format.DiffActionSymbol(element.action), element.Render(indent+1, elementOpts)))
|
||||
}
|
||||
|
||||
if unchangedElements > 0 {
|
||||
buf.WriteString(fmt.Sprintf("%s%s %s\n", change.indent(indent+1), change.emptySymbol(), change.unchanged("element", unchangedElements)))
|
||||
}
|
||||
|
||||
buf.WriteString(fmt.Sprintf("%s%s ]%s", change.indent(indent), change.emptySymbol(), change.nullSuffix(opts.overrideNullSuffix)))
|
||||
return buf.String()
|
||||
}
|
@ -170,6 +170,26 @@ func validateList(t *testing.T, list *listRenderer, elements []ValidateChangeFun
|
||||
}
|
||||
}
|
||||
|
||||
func ValidateSet(elements []ValidateChangeFunc, action plans.Action, replace bool) ValidateChangeFunc {
|
||||
return func(t *testing.T, change Change) {
|
||||
validateChange(t, change, action, replace)
|
||||
|
||||
set, ok := change.renderer.(*setRenderer)
|
||||
if !ok {
|
||||
t.Fatalf("invalid renderer type: %T", change.renderer)
|
||||
}
|
||||
|
||||
if len(set.elements) != len(elements) {
|
||||
t.Fatalf("expected %d elements but found %d elements", len(elements), len(set.elements))
|
||||
}
|
||||
|
||||
for ix := 0; ix < len(elements); ix++ {
|
||||
elements[ix](t, set.elements[ix])
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func ValidateSensitive(before, after interface{}, beforeSensitive, afterSensitive bool, action plans.Action, replace bool) ValidateChangeFunc {
|
||||
return func(t *testing.T, change Change) {
|
||||
validateChange(t, change, action, replace)
|
||||
|
@ -23,6 +23,8 @@ func (v Value) computeChangeForNestedAttribute(attribute *jsonprovider.NestedTyp
|
||||
return v.computeAttributeChangeAsNestedMap(attribute.Attributes)
|
||||
case "list":
|
||||
return v.computeAttributeChangeAsNestedList(attribute.Attributes)
|
||||
case "set":
|
||||
return v.computeAttributeChangeAsNestedSet(attribute.Attributes)
|
||||
default:
|
||||
panic("unrecognized nesting mode: " + attribute.NestingMode)
|
||||
}
|
||||
@ -38,6 +40,8 @@ func (v Value) computeChangeForType(ctyType cty.Type) change.Change {
|
||||
return v.computeAttributeChangeAsMap(ctyType.ElementType())
|
||||
case ctyType.IsListType():
|
||||
return v.computeAttributeChangeAsList(ctyType.ElementType())
|
||||
case ctyType.IsSetType():
|
||||
return v.computeAttributeChangeAsSet(ctyType.ElementType())
|
||||
default:
|
||||
panic("unrecognized type: " + ctyType.FriendlyName())
|
||||
}
|
||||
|
90
internal/command/jsonformat/differ/set.go
Normal file
90
internal/command/jsonformat/differ/set.go
Normal file
@ -0,0 +1,90 @@
|
||||
package differ
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/command/jsonformat/change"
|
||||
"github.com/hashicorp/terraform/internal/command/jsonprovider"
|
||||
)
|
||||
|
||||
func (v Value) computeAttributeChangeAsSet(elementType cty.Type) change.Change {
|
||||
var elements []change.Change
|
||||
current := v.getDefaultActionForIteration()
|
||||
v.processSet(false, func(value Value) {
|
||||
element := value.ComputeChange(elementType)
|
||||
elements = append(elements, element)
|
||||
current = compareActions(current, element.GetAction())
|
||||
})
|
||||
return change.New(change.Set(elements), current, v.replacePath())
|
||||
}
|
||||
|
||||
func (v Value) computeAttributeChangeAsNestedSet(attributes map[string]*jsonprovider.Attribute) change.Change {
|
||||
var elements []change.Change
|
||||
current := v.getDefaultActionForIteration()
|
||||
v.processSet(true, func(value Value) {
|
||||
element := value.ComputeChange(attributes)
|
||||
elements = append(elements, element)
|
||||
current = compareActions(current, element.GetAction())
|
||||
})
|
||||
return change.New(change.Set(elements), current, v.replacePath())
|
||||
}
|
||||
|
||||
func (v Value) processSet(propagateReplace bool, process func(value Value)) {
|
||||
sliceValue := v.asSlice()
|
||||
|
||||
foundInBefore := make(map[int]int)
|
||||
foundInAfter := make(map[int]int)
|
||||
|
||||
// O(n^2) operation here to find matching pairs in the set, so we can make
|
||||
// the display look pretty. There might be a better way to do this, so look
|
||||
// here for potential optimisations.
|
||||
|
||||
for ix := 0; ix < len(sliceValue.Before); ix++ {
|
||||
matched := false
|
||||
for jx := 0; jx < len(sliceValue.After); jx++ {
|
||||
if _, ok := foundInAfter[jx]; ok {
|
||||
// We've already found a match for this after value.
|
||||
continue
|
||||
}
|
||||
|
||||
child := sliceValue.getChild(ix, jx, propagateReplace)
|
||||
if reflect.DeepEqual(child.Before, child.After) && child.isBeforeSensitive() == child.isAfterSensitive() && child.Unknown == nil {
|
||||
matched = true
|
||||
foundInBefore[ix] = jx
|
||||
foundInAfter[jx] = ix
|
||||
}
|
||||
}
|
||||
|
||||
if !matched {
|
||||
foundInBefore[ix] = -1
|
||||
}
|
||||
}
|
||||
|
||||
// Now everything in before should be a key in foundInBefore and a value
|
||||
// in foundInAfter. If a key is mapped to -1 in foundInBefore it means it
|
||||
// does not have an equivalent in foundInAfter and so has been deleted.
|
||||
// Everything in foundInAfter has a matching value in foundInBefore, but
|
||||
// some values in after may not be in foundInAfter. This means these values
|
||||
// are newly created.
|
||||
|
||||
for ix := 0; ix < len(sliceValue.Before); ix++ {
|
||||
if jx := foundInBefore[ix]; jx >= 0 {
|
||||
child := sliceValue.getChild(ix, jx, propagateReplace)
|
||||
process(child)
|
||||
continue
|
||||
}
|
||||
child := sliceValue.getChild(ix, len(sliceValue.After), propagateReplace)
|
||||
process(child)
|
||||
}
|
||||
|
||||
for jx := 0; jx < len(sliceValue.After); jx++ {
|
||||
if _, ok := foundInAfter[jx]; ok {
|
||||
// Then this value was handled in the previous for loop.
|
||||
continue
|
||||
}
|
||||
child := sliceValue.getChild(len(sliceValue.Before), jx, propagateReplace)
|
||||
process(child)
|
||||
}
|
||||
}
|
@ -13,6 +13,27 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
)
|
||||
|
||||
type SetChange struct {
|
||||
Before SetChangeEntry
|
||||
After SetChangeEntry
|
||||
}
|
||||
|
||||
type SetChangeEntry struct {
|
||||
SingleChange change.ValidateChangeFunc
|
||||
ObjectChange map[string]change.ValidateChangeFunc
|
||||
|
||||
Replace bool
|
||||
Action plans.Action
|
||||
}
|
||||
|
||||
func (entry SetChangeEntry) Validate(obj func(attributes map[string]change.ValidateChangeFunc, action plans.Action, replace bool) change.ValidateChangeFunc) change.ValidateChangeFunc {
|
||||
if entry.SingleChange != nil {
|
||||
return entry.SingleChange
|
||||
}
|
||||
|
||||
return obj(entry.ObjectChange, entry.Action, entry.Replace)
|
||||
}
|
||||
|
||||
func TestValue_ObjectAttributes(t *testing.T) {
|
||||
// This function holds a range of test cases creating, deleting and editing
|
||||
// objects. It is built in such a way that it can automatically test these
|
||||
@ -28,6 +49,9 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
validateChanges map[string]change.ValidateChangeFunc
|
||||
validateReplace bool
|
||||
validateAction plans.Action
|
||||
// Sets break changes out differently to the other collections, so they
|
||||
// have their own entry.
|
||||
validateSetChanges *SetChange
|
||||
}{
|
||||
"create": {
|
||||
input: Value{
|
||||
@ -119,6 +143,18 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
validateNestedObject: change.ValidateComputed(change.ValidateNestedObject(map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
||||
}, plans.Delete, false), plans.Update, false),
|
||||
validateSetChanges: &SetChange{
|
||||
Before: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
||||
},
|
||||
Action: plans.Delete,
|
||||
Replace: false,
|
||||
},
|
||||
After: SetChangeEntry{
|
||||
SingleChange: change.ValidateComputed(nil, plans.Create, false),
|
||||
},
|
||||
},
|
||||
},
|
||||
"create_attribute": {
|
||||
input: Value{
|
||||
@ -135,6 +171,20 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
},
|
||||
validateAction: plans.Update,
|
||||
validateReplace: false,
|
||||
validateSetChanges: &SetChange{
|
||||
Before: SetChangeEntry{
|
||||
ObjectChange: nil,
|
||||
Action: plans.Delete,
|
||||
Replace: false,
|
||||
},
|
||||
After: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidatePrimitive(nil, strptr("\"new\""), plans.Create, false),
|
||||
},
|
||||
Action: plans.Create,
|
||||
Replace: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"create_attribute_from_explicit_null": {
|
||||
input: Value{
|
||||
@ -153,6 +203,20 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
},
|
||||
validateAction: plans.Update,
|
||||
validateReplace: false,
|
||||
validateSetChanges: &SetChange{
|
||||
Before: SetChangeEntry{
|
||||
ObjectChange: nil,
|
||||
Action: plans.Delete,
|
||||
Replace: false,
|
||||
},
|
||||
After: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidatePrimitive(nil, strptr("\"new\""), plans.Create, false),
|
||||
},
|
||||
Action: plans.Create,
|
||||
Replace: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"delete_attribute": {
|
||||
input: Value{
|
||||
@ -169,6 +233,20 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
},
|
||||
validateAction: plans.Update,
|
||||
validateReplace: false,
|
||||
validateSetChanges: &SetChange{
|
||||
Before: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
||||
},
|
||||
Action: plans.Delete,
|
||||
Replace: false,
|
||||
},
|
||||
After: SetChangeEntry{
|
||||
ObjectChange: nil,
|
||||
Action: plans.Create,
|
||||
Replace: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"delete_attribute_to_explicit_null": {
|
||||
input: Value{
|
||||
@ -187,6 +265,20 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
},
|
||||
validateAction: plans.Update,
|
||||
validateReplace: false,
|
||||
validateSetChanges: &SetChange{
|
||||
Before: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
||||
},
|
||||
Action: plans.Delete,
|
||||
Replace: false,
|
||||
},
|
||||
After: SetChangeEntry{
|
||||
ObjectChange: nil,
|
||||
Action: plans.Create,
|
||||
Replace: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"update_attribute": {
|
||||
input: Value{
|
||||
@ -205,6 +297,22 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
},
|
||||
validateAction: plans.Update,
|
||||
validateReplace: false,
|
||||
validateSetChanges: &SetChange{
|
||||
Before: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
||||
},
|
||||
Action: plans.Delete,
|
||||
Replace: false,
|
||||
},
|
||||
After: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidatePrimitive(nil, strptr("\"new\""), plans.Create, false),
|
||||
},
|
||||
Action: plans.Create,
|
||||
Replace: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"create_sensitive_attribute": {
|
||||
input: Value{
|
||||
@ -224,6 +332,20 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
},
|
||||
validateAction: plans.Update,
|
||||
validateReplace: false,
|
||||
validateSetChanges: &SetChange{
|
||||
Before: SetChangeEntry{
|
||||
ObjectChange: nil,
|
||||
Action: plans.Delete,
|
||||
Replace: false,
|
||||
},
|
||||
After: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidateSensitive(nil, "new", false, true, plans.Create, false),
|
||||
},
|
||||
Action: plans.Create,
|
||||
Replace: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"delete_sensitive_attribute": {
|
||||
input: Value{
|
||||
@ -243,6 +365,20 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
},
|
||||
validateAction: plans.Update,
|
||||
validateReplace: false,
|
||||
validateSetChanges: &SetChange{
|
||||
Before: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidateSensitive("old", nil, true, false, plans.Delete, false),
|
||||
},
|
||||
Action: plans.Delete,
|
||||
Replace: false,
|
||||
},
|
||||
After: SetChangeEntry{
|
||||
ObjectChange: nil,
|
||||
Action: plans.Create,
|
||||
Replace: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"update_sensitive_attribute": {
|
||||
input: Value{
|
||||
@ -267,6 +403,22 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
},
|
||||
validateAction: plans.Update,
|
||||
validateReplace: false,
|
||||
validateSetChanges: &SetChange{
|
||||
Before: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidateSensitive("old", nil, true, false, plans.Delete, false),
|
||||
},
|
||||
Action: plans.Delete,
|
||||
Replace: false,
|
||||
},
|
||||
After: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidateSensitive(nil, "new", false, true, plans.Create, false),
|
||||
},
|
||||
Action: plans.Create,
|
||||
Replace: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"create_computed_attribute": {
|
||||
input: Value{
|
||||
@ -284,6 +436,20 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
},
|
||||
validateAction: plans.Update,
|
||||
validateReplace: false,
|
||||
validateSetChanges: &SetChange{
|
||||
Before: SetChangeEntry{
|
||||
ObjectChange: nil,
|
||||
Action: plans.Delete,
|
||||
Replace: false,
|
||||
},
|
||||
After: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidateComputed(nil, plans.Create, false),
|
||||
},
|
||||
Action: plans.Create,
|
||||
Replace: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"update_computed_attribute": {
|
||||
input: Value{
|
||||
@ -306,6 +472,22 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
},
|
||||
validateAction: plans.Update,
|
||||
validateReplace: false,
|
||||
validateSetChanges: &SetChange{
|
||||
Before: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
||||
},
|
||||
Action: plans.Delete,
|
||||
Replace: false,
|
||||
},
|
||||
After: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidateComputed(nil, plans.Create, false),
|
||||
},
|
||||
Action: plans.Create,
|
||||
Replace: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"ignores_unset_fields": {
|
||||
input: Value{
|
||||
@ -339,6 +521,22 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
},
|
||||
validateAction: plans.Update,
|
||||
validateReplace: true,
|
||||
validateSetChanges: &SetChange{
|
||||
Before: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
||||
},
|
||||
Action: plans.Delete,
|
||||
Replace: true,
|
||||
},
|
||||
After: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidatePrimitive(nil, strptr("\"new\""), plans.Create, false),
|
||||
},
|
||||
Action: plans.Create,
|
||||
Replace: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"update_replace_attribute": {
|
||||
input: Value{
|
||||
@ -360,6 +558,22 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
},
|
||||
validateAction: plans.Update,
|
||||
validateReplace: false,
|
||||
validateSetChanges: &SetChange{
|
||||
Before: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, true),
|
||||
},
|
||||
Action: plans.Delete,
|
||||
Replace: false,
|
||||
},
|
||||
After: SetChangeEntry{
|
||||
ObjectChange: map[string]change.ValidateChangeFunc{
|
||||
"attribute_one": change.ValidatePrimitive(nil, strptr("\"new\""), plans.Create, false),
|
||||
},
|
||||
Action: plans.Create,
|
||||
Replace: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -450,6 +664,46 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, input.ComputeChange(attribute))
|
||||
})
|
||||
|
||||
t.Run("set", func(t *testing.T) {
|
||||
attribute := &jsonprovider.Attribute{
|
||||
AttributeType: unmarshalType(t, cty.Set(cty.Object(tc.attributes))),
|
||||
}
|
||||
|
||||
input := wrapValueInSlice(tc.input)
|
||||
|
||||
if tc.validateSetChanges != nil {
|
||||
validate := change.ValidateSet(func() []change.ValidateChangeFunc {
|
||||
var ret []change.ValidateChangeFunc
|
||||
ret = append(ret, tc.validateSetChanges.Before.Validate(change.ValidateObject))
|
||||
ret = append(ret, tc.validateSetChanges.After.Validate(change.ValidateObject))
|
||||
return ret
|
||||
}(), collectionDefaultAction, false)
|
||||
validate(t, input.ComputeChange(attribute))
|
||||
return
|
||||
}
|
||||
|
||||
if tc.validateObject != nil {
|
||||
validate := change.ValidateSet([]change.ValidateChangeFunc{
|
||||
tc.validateObject,
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, input.ComputeChange(attribute))
|
||||
return
|
||||
}
|
||||
|
||||
if tc.validateSingleChange != nil {
|
||||
validate := change.ValidateSet([]change.ValidateChangeFunc{
|
||||
tc.validateSingleChange,
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, input.ComputeChange(attribute))
|
||||
return
|
||||
}
|
||||
|
||||
validate := change.ValidateSet([]change.ValidateChangeFunc{
|
||||
change.ValidateObject(tc.validateChanges, tc.validateAction, tc.validateReplace),
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, input.ComputeChange(attribute))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run(fmt.Sprintf("nested_%s", name), func(t *testing.T) {
|
||||
@ -562,6 +816,57 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, input.ComputeChange(attribute))
|
||||
})
|
||||
|
||||
t.Run("set", func(t *testing.T) {
|
||||
attribute := &jsonprovider.Attribute{
|
||||
AttributeNestedType: &jsonprovider.NestedType{
|
||||
Attributes: func() map[string]*jsonprovider.Attribute {
|
||||
attributes := make(map[string]*jsonprovider.Attribute)
|
||||
for key, attribute := range tc.attributes {
|
||||
attributes[key] = &jsonprovider.Attribute{
|
||||
AttributeType: unmarshalType(t, attribute),
|
||||
}
|
||||
}
|
||||
return attributes
|
||||
}(),
|
||||
NestingMode: "set",
|
||||
},
|
||||
}
|
||||
|
||||
input := wrapValueInSlice(tc.input)
|
||||
|
||||
if tc.validateSetChanges != nil {
|
||||
validate := change.ValidateSet(func() []change.ValidateChangeFunc {
|
||||
var ret []change.ValidateChangeFunc
|
||||
ret = append(ret, tc.validateSetChanges.Before.Validate(change.ValidateNestedObject))
|
||||
ret = append(ret, tc.validateSetChanges.After.Validate(change.ValidateNestedObject))
|
||||
return ret
|
||||
}(), collectionDefaultAction, false)
|
||||
validate(t, input.ComputeChange(attribute))
|
||||
return
|
||||
}
|
||||
|
||||
if tc.validateNestedObject != nil {
|
||||
validate := change.ValidateSet([]change.ValidateChangeFunc{
|
||||
tc.validateNestedObject,
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, input.ComputeChange(attribute))
|
||||
return
|
||||
}
|
||||
|
||||
if tc.validateSingleChange != nil {
|
||||
validate := change.ValidateSet([]change.ValidateChangeFunc{
|
||||
tc.validateSingleChange,
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, input.ComputeChange(attribute))
|
||||
return
|
||||
}
|
||||
|
||||
validate := change.ValidateSet([]change.ValidateChangeFunc{
|
||||
change.ValidateNestedObject(tc.validateChanges, tc.validateAction, tc.validateReplace),
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, input.ComputeChange(attribute))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -572,10 +877,10 @@ func TestValue_PrimitiveAttributes(t *testing.T) {
|
||||
// contexts of collections.
|
||||
|
||||
tcs := map[string]struct {
|
||||
input Value
|
||||
attribute cty.Type
|
||||
validateChange change.ValidateChangeFunc
|
||||
validateListChanges []change.ValidateChangeFunc // Lists are special in some cases.
|
||||
input Value
|
||||
attribute cty.Type
|
||||
validateChange change.ValidateChangeFunc
|
||||
validateSliceChanges []change.ValidateChangeFunc // Lists are special in some cases.
|
||||
}{
|
||||
"primitive_create": {
|
||||
input: Value{
|
||||
@ -598,7 +903,7 @@ func TestValue_PrimitiveAttributes(t *testing.T) {
|
||||
},
|
||||
attribute: cty.String,
|
||||
validateChange: change.ValidatePrimitive(strptr("\"old\""), strptr("\"new\""), plans.Update, false),
|
||||
validateListChanges: []change.ValidateChangeFunc{
|
||||
validateSliceChanges: []change.ValidateChangeFunc{
|
||||
change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
||||
change.ValidatePrimitive(nil, strptr("\"new\""), plans.Create, false),
|
||||
},
|
||||
@ -611,7 +916,7 @@ func TestValue_PrimitiveAttributes(t *testing.T) {
|
||||
},
|
||||
attribute: cty.String,
|
||||
validateChange: change.ValidatePrimitive(strptr("\"old\""), nil, plans.Update, false),
|
||||
validateListChanges: []change.ValidateChangeFunc{
|
||||
validateSliceChanges: []change.ValidateChangeFunc{
|
||||
change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
||||
change.ValidatePrimitive(nil, nil, plans.Create, false),
|
||||
},
|
||||
@ -624,7 +929,7 @@ func TestValue_PrimitiveAttributes(t *testing.T) {
|
||||
},
|
||||
attribute: cty.String,
|
||||
validateChange: change.ValidatePrimitive(nil, strptr("\"new\""), plans.Update, false),
|
||||
validateListChanges: []change.ValidateChangeFunc{
|
||||
validateSliceChanges: []change.ValidateChangeFunc{
|
||||
change.ValidatePrimitive(nil, nil, plans.Delete, false),
|
||||
change.ValidatePrimitive(nil, strptr("\"new\""), plans.Create, false),
|
||||
},
|
||||
@ -656,7 +961,7 @@ func TestValue_PrimitiveAttributes(t *testing.T) {
|
||||
},
|
||||
attribute: cty.String,
|
||||
validateChange: change.ValidateSensitive("old", "new", true, true, plans.Update, false),
|
||||
validateListChanges: []change.ValidateChangeFunc{
|
||||
validateSliceChanges: []change.ValidateChangeFunc{
|
||||
change.ValidateSensitive("old", nil, true, false, plans.Delete, false),
|
||||
change.ValidateSensitive(nil, "new", false, true, plans.Create, false),
|
||||
},
|
||||
@ -678,7 +983,7 @@ func TestValue_PrimitiveAttributes(t *testing.T) {
|
||||
},
|
||||
attribute: cty.String,
|
||||
validateChange: change.ValidateComputed(change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false), plans.Update, false),
|
||||
validateListChanges: []change.ValidateChangeFunc{
|
||||
validateSliceChanges: []change.ValidateChangeFunc{
|
||||
change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
||||
change.ValidateComputed(nil, plans.Create, false),
|
||||
},
|
||||
@ -693,7 +998,7 @@ func TestValue_PrimitiveAttributes(t *testing.T) {
|
||||
},
|
||||
attribute: cty.String,
|
||||
validateChange: change.ValidatePrimitive(strptr("\"old\""), strptr("\"new\""), plans.Update, true),
|
||||
validateListChanges: []change.ValidateChangeFunc{
|
||||
validateSliceChanges: []change.ValidateChangeFunc{
|
||||
change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, true),
|
||||
change.ValidatePrimitive(nil, strptr("\"new\""), plans.Create, false),
|
||||
},
|
||||
@ -740,8 +1045,8 @@ func TestValue_PrimitiveAttributes(t *testing.T) {
|
||||
AttributeType: unmarshalType(t, cty.List(tc.attribute)),
|
||||
}
|
||||
|
||||
if tc.validateListChanges != nil {
|
||||
validate := change.ValidateList(tc.validateListChanges, defaultCollectionsAction, false)
|
||||
if tc.validateSliceChanges != nil {
|
||||
validate := change.ValidateList(tc.validateSliceChanges, defaultCollectionsAction, false)
|
||||
validate(t, input.ComputeChange(attribute))
|
||||
return
|
||||
}
|
||||
@ -751,6 +1056,24 @@ func TestValue_PrimitiveAttributes(t *testing.T) {
|
||||
}, defaultCollectionsAction, false)
|
||||
validate(t, input.ComputeChange(attribute))
|
||||
})
|
||||
|
||||
t.Run("set", func(t *testing.T) {
|
||||
input := wrapValueInSlice(tc.input)
|
||||
attribute := &jsonprovider.Attribute{
|
||||
AttributeType: unmarshalType(t, cty.Set(tc.attribute)),
|
||||
}
|
||||
|
||||
if tc.validateSliceChanges != nil {
|
||||
validate := change.ValidateSet(tc.validateSliceChanges, defaultCollectionsAction, false)
|
||||
validate(t, input.ComputeChange(attribute))
|
||||
return
|
||||
}
|
||||
|
||||
validate := change.ValidateSet([]change.ValidateChangeFunc{
|
||||
tc.validateChange,
|
||||
}, defaultCollectionsAction, false)
|
||||
validate(t, input.ComputeChange(attribute))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -978,6 +1301,108 @@ func TestValue_CollectionAttributes(t *testing.T) {
|
||||
},
|
||||
validateChange: change.ValidateComputed(change.ValidateList(nil, plans.Delete, false), plans.Update, false),
|
||||
},
|
||||
"set_create_empty": {
|
||||
input: Value{
|
||||
Before: nil,
|
||||
After: []interface{}{},
|
||||
},
|
||||
attribute: &jsonprovider.Attribute{
|
||||
AttributeType: unmarshalType(t, cty.Set(cty.String)),
|
||||
},
|
||||
validateChange: change.ValidateSet(nil, plans.Create, false),
|
||||
},
|
||||
"set_create_populated": {
|
||||
input: Value{
|
||||
Before: nil,
|
||||
After: []interface{}{"one", "two"},
|
||||
},
|
||||
attribute: &jsonprovider.Attribute{
|
||||
AttributeType: unmarshalType(t, cty.Set(cty.String)),
|
||||
},
|
||||
validateChange: change.ValidateSet([]change.ValidateChangeFunc{
|
||||
change.ValidatePrimitive(nil, strptr("\"one\""), plans.Create, false),
|
||||
change.ValidatePrimitive(nil, strptr("\"two\""), plans.Create, false),
|
||||
}, plans.Create, false),
|
||||
},
|
||||
"set_delete_empty": {
|
||||
input: Value{
|
||||
Before: []interface{}{},
|
||||
After: nil,
|
||||
},
|
||||
attribute: &jsonprovider.Attribute{
|
||||
AttributeType: unmarshalType(t, cty.Set(cty.String)),
|
||||
},
|
||||
validateChange: change.ValidateSet(nil, plans.Delete, false),
|
||||
},
|
||||
"set_delete_populated": {
|
||||
input: Value{
|
||||
Before: []interface{}{"one", "two"},
|
||||
After: nil,
|
||||
},
|
||||
attribute: &jsonprovider.Attribute{
|
||||
AttributeType: unmarshalType(t, cty.Set(cty.String)),
|
||||
},
|
||||
validateChange: change.ValidateSet([]change.ValidateChangeFunc{
|
||||
change.ValidatePrimitive(strptr("\"one\""), nil, plans.Delete, false),
|
||||
change.ValidatePrimitive(strptr("\"two\""), nil, plans.Delete, false),
|
||||
}, plans.Delete, false),
|
||||
},
|
||||
"set_create_sensitive": {
|
||||
input: Value{
|
||||
Before: nil,
|
||||
After: []interface{}{},
|
||||
AfterSensitive: true,
|
||||
},
|
||||
attribute: &jsonprovider.Attribute{
|
||||
AttributeType: unmarshalType(t, cty.Set(cty.String)),
|
||||
},
|
||||
validateChange: change.ValidateSensitive(nil, []interface{}{}, false, true, plans.Create, false),
|
||||
},
|
||||
"set_update_sensitive": {
|
||||
input: Value{
|
||||
Before: []interface{}{"one"},
|
||||
BeforeSensitive: true,
|
||||
After: []interface{}{},
|
||||
AfterSensitive: true,
|
||||
},
|
||||
attribute: &jsonprovider.Attribute{
|
||||
AttributeType: unmarshalType(t, cty.Set(cty.String)),
|
||||
},
|
||||
validateChange: change.ValidateSensitive([]interface{}{"one"}, []interface{}{}, true, true, plans.Update, false),
|
||||
},
|
||||
"set_delete_sensitive": {
|
||||
input: Value{
|
||||
Before: []interface{}{},
|
||||
BeforeSensitive: true,
|
||||
After: nil,
|
||||
},
|
||||
attribute: &jsonprovider.Attribute{
|
||||
AttributeType: unmarshalType(t, cty.Set(cty.String)),
|
||||
},
|
||||
validateChange: change.ValidateSensitive([]interface{}{}, nil, true, false, plans.Delete, false),
|
||||
},
|
||||
"set_create_unknown": {
|
||||
input: Value{
|
||||
Before: nil,
|
||||
After: []interface{}{},
|
||||
Unknown: true,
|
||||
},
|
||||
attribute: &jsonprovider.Attribute{
|
||||
AttributeType: unmarshalType(t, cty.Set(cty.String)),
|
||||
},
|
||||
validateChange: change.ValidateComputed(nil, plans.Create, false),
|
||||
},
|
||||
"set_update_unknown": {
|
||||
input: Value{
|
||||
Before: []interface{}{},
|
||||
After: []interface{}{"one"},
|
||||
Unknown: true,
|
||||
},
|
||||
attribute: &jsonprovider.Attribute{
|
||||
AttributeType: unmarshalType(t, cty.Set(cty.String)),
|
||||
},
|
||||
validateChange: change.ValidateComputed(change.ValidateSet(nil, plans.Delete, false), plans.Update, false),
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tcs {
|
||||
|
Loading…
Reference in New Issue
Block a user