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:
Liam Cervante 2023-01-09 14:17:30 +01:00 committed by GitHub
parent aff94591c1
commit 69cce3597f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 870 additions and 15 deletions

View File

@ -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
}

View File

@ -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),
]
`,
},
}

View 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()
}

View File

@ -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)

View File

@ -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())
}

View 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)
}
}

View File

@ -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 {