mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-23 03:34:30 -05:00
* 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
502 lines
14 KiB
Go
502 lines
14 KiB
Go
package differ
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
ctyjson "github.com/zclconf/go-cty/cty/json"
|
|
|
|
"github.com/hashicorp/terraform/internal/command/jsonformat/change"
|
|
"github.com/hashicorp/terraform/internal/command/jsonprovider"
|
|
"github.com/hashicorp/terraform/internal/plans"
|
|
)
|
|
|
|
func TestValue_ObjectAttributes(t *testing.T) {
|
|
// We break these tests out into their own function, so we can automatically
|
|
// test both objects and nested objects together.
|
|
|
|
tcs := map[string]struct {
|
|
input Value
|
|
attributes map[string]cty.Type
|
|
validateSingleChange change.ValidateChangeFunc
|
|
validateObject change.ValidateChangeFunc
|
|
validateNestedObject change.ValidateChangeFunc
|
|
validateChanges map[string]change.ValidateChangeFunc
|
|
validateReplace bool
|
|
validateAction plans.Action
|
|
}{
|
|
"object_create": {
|
|
input: Value{
|
|
Before: nil,
|
|
After: map[string]interface{}{
|
|
"attribute_one": "new",
|
|
},
|
|
},
|
|
attributes: map[string]cty.Type{
|
|
"attribute_one": cty.String,
|
|
},
|
|
validateChanges: map[string]change.ValidateChangeFunc{
|
|
"attribute_one": change.ValidatePrimitive(nil, strptr("\"new\""), plans.Create, false),
|
|
},
|
|
validateAction: plans.Create,
|
|
validateReplace: false,
|
|
},
|
|
"object_delete": {
|
|
input: Value{
|
|
Before: map[string]interface{}{
|
|
"attribute_one": "old",
|
|
},
|
|
After: nil,
|
|
},
|
|
attributes: map[string]cty.Type{
|
|
"attribute_one": cty.String,
|
|
},
|
|
validateChanges: map[string]change.ValidateChangeFunc{
|
|
"attribute_one": change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
|
},
|
|
validateAction: plans.Delete,
|
|
validateReplace: false,
|
|
},
|
|
"object_create_sensitive": {
|
|
input: Value{
|
|
Before: nil,
|
|
After: map[string]interface{}{
|
|
"attribute_one": "new",
|
|
},
|
|
AfterSensitive: true,
|
|
},
|
|
attributes: map[string]cty.Type{
|
|
"attribute_one": cty.String,
|
|
},
|
|
validateSingleChange: change.ValidateSensitive(nil, map[string]interface{}{
|
|
"attribute_one": "new",
|
|
}, false, true, plans.Create, false),
|
|
},
|
|
"object_delete_sensitive": {
|
|
input: Value{
|
|
Before: map[string]interface{}{
|
|
"attribute_one": "old",
|
|
},
|
|
BeforeSensitive: true,
|
|
After: nil,
|
|
},
|
|
attributes: map[string]cty.Type{
|
|
"attribute_one": cty.String,
|
|
},
|
|
validateSingleChange: change.ValidateSensitive(map[string]interface{}{
|
|
"attribute_one": "old",
|
|
}, nil, true, false, plans.Delete, false),
|
|
},
|
|
"object_create_unknown": {
|
|
input: Value{
|
|
Before: nil,
|
|
After: nil,
|
|
Unknown: true,
|
|
},
|
|
attributes: map[string]cty.Type{
|
|
"attribute_one": cty.String,
|
|
},
|
|
validateSingleChange: change.ValidateComputed(nil, plans.Create, false),
|
|
},
|
|
"object_update_unknown": {
|
|
input: Value{
|
|
Before: map[string]interface{}{
|
|
"attribute_one": "old",
|
|
},
|
|
After: nil,
|
|
Unknown: true,
|
|
},
|
|
attributes: map[string]cty.Type{
|
|
"attribute_one": cty.String,
|
|
},
|
|
validateObject: change.ValidateComputed(change.ValidateObject(map[string]change.ValidateChangeFunc{
|
|
"attribute_one": change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
|
}, plans.Delete, false), plans.Update, false),
|
|
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),
|
|
},
|
|
"object_create_attribute": {
|
|
input: Value{
|
|
Before: map[string]interface{}{},
|
|
After: map[string]interface{}{
|
|
"attribute_one": "new",
|
|
},
|
|
},
|
|
attributes: map[string]cty.Type{
|
|
"attribute_one": cty.String,
|
|
},
|
|
validateChanges: map[string]change.ValidateChangeFunc{
|
|
"attribute_one": change.ValidatePrimitive(nil, strptr("\"new\""), plans.Create, false),
|
|
},
|
|
validateAction: plans.Update,
|
|
validateReplace: false,
|
|
},
|
|
"object_create_attribute_from_explicit_null": {
|
|
input: Value{
|
|
Before: map[string]interface{}{
|
|
"attribute_one": nil,
|
|
},
|
|
After: map[string]interface{}{
|
|
"attribute_one": "new",
|
|
},
|
|
},
|
|
attributes: map[string]cty.Type{
|
|
"attribute_one": cty.String,
|
|
},
|
|
validateChanges: map[string]change.ValidateChangeFunc{
|
|
"attribute_one": change.ValidatePrimitive(nil, strptr("\"new\""), plans.Create, false),
|
|
},
|
|
validateAction: plans.Update,
|
|
validateReplace: false,
|
|
},
|
|
"object_delete_attribute": {
|
|
input: Value{
|
|
Before: map[string]interface{}{
|
|
"attribute_one": "old",
|
|
},
|
|
After: map[string]interface{}{},
|
|
},
|
|
attributes: map[string]cty.Type{
|
|
"attribute_one": cty.String,
|
|
},
|
|
validateChanges: map[string]change.ValidateChangeFunc{
|
|
"attribute_one": change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
|
},
|
|
validateAction: plans.Update,
|
|
validateReplace: false,
|
|
},
|
|
"object_delete_attribute_to_explicit_null": {
|
|
input: Value{
|
|
Before: map[string]interface{}{
|
|
"attribute_one": "old",
|
|
},
|
|
After: map[string]interface{}{
|
|
"attribute_one": nil,
|
|
},
|
|
},
|
|
attributes: map[string]cty.Type{
|
|
"attribute_one": cty.String,
|
|
},
|
|
validateChanges: map[string]change.ValidateChangeFunc{
|
|
"attribute_one": change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
|
},
|
|
validateAction: plans.Update,
|
|
validateReplace: false,
|
|
},
|
|
"object_update_attribute": {
|
|
input: Value{
|
|
Before: map[string]interface{}{
|
|
"attribute_one": "old",
|
|
},
|
|
After: map[string]interface{}{
|
|
"attribute_one": "new",
|
|
},
|
|
},
|
|
attributes: map[string]cty.Type{
|
|
"attribute_one": cty.String,
|
|
},
|
|
validateChanges: map[string]change.ValidateChangeFunc{
|
|
"attribute_one": change.ValidatePrimitive(strptr("\"old\""), strptr("\"new\""), plans.Update, false),
|
|
},
|
|
validateAction: plans.Update,
|
|
validateReplace: false,
|
|
},
|
|
"object_create_sensitive_attribute": {
|
|
input: Value{
|
|
Before: map[string]interface{}{},
|
|
After: map[string]interface{}{
|
|
"attribute_one": "new",
|
|
},
|
|
AfterSensitive: map[string]interface{}{
|
|
"attribute_one": true,
|
|
},
|
|
},
|
|
attributes: map[string]cty.Type{
|
|
"attribute_one": cty.String,
|
|
},
|
|
validateChanges: map[string]change.ValidateChangeFunc{
|
|
"attribute_one": change.ValidateSensitive(nil, "new", false, true, plans.Create, false),
|
|
},
|
|
validateAction: plans.Update,
|
|
validateReplace: false,
|
|
},
|
|
"object_delete_sensitive_attribute": {
|
|
input: Value{
|
|
Before: map[string]interface{}{
|
|
"attribute_one": "old",
|
|
},
|
|
BeforeSensitive: map[string]interface{}{
|
|
"attribute_one": true,
|
|
},
|
|
After: map[string]interface{}{},
|
|
},
|
|
attributes: map[string]cty.Type{
|
|
"attribute_one": cty.String,
|
|
},
|
|
validateChanges: map[string]change.ValidateChangeFunc{
|
|
"attribute_one": change.ValidateSensitive("old", nil, true, false, plans.Delete, false),
|
|
},
|
|
validateAction: plans.Update,
|
|
validateReplace: false,
|
|
},
|
|
"object_update_sensitive_attribute": {
|
|
input: Value{
|
|
Before: map[string]interface{}{
|
|
"attribute_one": "old",
|
|
},
|
|
BeforeSensitive: map[string]interface{}{
|
|
"attribute_one": true,
|
|
},
|
|
After: map[string]interface{}{
|
|
"attribute_one": "new",
|
|
},
|
|
AfterSensitive: map[string]interface{}{
|
|
"attribute_one": true,
|
|
},
|
|
},
|
|
attributes: map[string]cty.Type{
|
|
"attribute_one": cty.String,
|
|
},
|
|
validateChanges: map[string]change.ValidateChangeFunc{
|
|
"attribute_one": change.ValidateSensitive("old", "new", true, true, plans.Update, false),
|
|
},
|
|
validateAction: plans.Update,
|
|
validateReplace: false,
|
|
},
|
|
"object_create_computed_attribute": {
|
|
input: Value{
|
|
Before: map[string]interface{}{},
|
|
After: map[string]interface{}{},
|
|
Unknown: map[string]interface{}{
|
|
"attribute_one": true,
|
|
},
|
|
},
|
|
attributes: map[string]cty.Type{
|
|
"attribute_one": cty.String,
|
|
},
|
|
validateChanges: map[string]change.ValidateChangeFunc{
|
|
"attribute_one": change.ValidateComputed(nil, plans.Create, false),
|
|
},
|
|
validateAction: plans.Update,
|
|
validateReplace: false,
|
|
},
|
|
"object_update_computed_attribute": {
|
|
input: Value{
|
|
Before: map[string]interface{}{
|
|
"attribute_one": "old",
|
|
},
|
|
After: map[string]interface{}{},
|
|
Unknown: map[string]interface{}{
|
|
"attribute_one": true,
|
|
},
|
|
},
|
|
attributes: map[string]cty.Type{
|
|
"attribute_one": cty.String,
|
|
},
|
|
validateChanges: map[string]change.ValidateChangeFunc{
|
|
"attribute_one": change.ValidateComputed(
|
|
change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
|
plans.Update,
|
|
false),
|
|
},
|
|
validateAction: plans.Update,
|
|
validateReplace: false,
|
|
},
|
|
"object_ignores_unset_fields": {
|
|
input: Value{
|
|
Before: map[string]interface{}{},
|
|
After: map[string]interface{}{},
|
|
},
|
|
attributes: map[string]cty.Type{
|
|
"attribute_one": cty.String,
|
|
},
|
|
validateChanges: map[string]change.ValidateChangeFunc{},
|
|
validateAction: plans.NoOp,
|
|
validateReplace: false,
|
|
},
|
|
}
|
|
|
|
for name, tc := range tcs {
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
attribute := &jsonprovider.Attribute{
|
|
AttributeType: unmarshalType(t, cty.Object(tc.attributes)),
|
|
}
|
|
|
|
if tc.validateObject != nil {
|
|
tc.validateObject(t, tc.input.ComputeChange(attribute))
|
|
return
|
|
}
|
|
|
|
if tc.validateSingleChange != nil {
|
|
tc.validateSingleChange(t, tc.input.ComputeChange(attribute))
|
|
return
|
|
}
|
|
|
|
validate := change.ValidateObject(tc.validateChanges, tc.validateAction, tc.validateReplace)
|
|
validate(t, tc.input.ComputeChange(attribute))
|
|
})
|
|
|
|
t.Run(fmt.Sprintf("nested_%s", name), 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: "single",
|
|
},
|
|
}
|
|
|
|
if tc.validateNestedObject != nil {
|
|
tc.validateNestedObject(t, tc.input.ComputeChange(attribute))
|
|
return
|
|
}
|
|
|
|
if tc.validateSingleChange != nil {
|
|
tc.validateSingleChange(t, tc.input.ComputeChange(attribute))
|
|
return
|
|
}
|
|
|
|
validate := change.ValidateNestedObject(tc.validateChanges, tc.validateAction, tc.validateReplace)
|
|
validate(t, tc.input.ComputeChange(attribute))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValue_Attribute(t *testing.T) {
|
|
tcs := map[string]struct {
|
|
input Value
|
|
attribute *jsonprovider.Attribute
|
|
validateChange change.ValidateChangeFunc
|
|
}{
|
|
"primitive_create": {
|
|
input: Value{
|
|
After: "new",
|
|
},
|
|
attribute: &jsonprovider.Attribute{
|
|
AttributeType: []byte("\"string\""),
|
|
},
|
|
validateChange: change.ValidatePrimitive(nil, strptr("\"new\""), plans.Create, false),
|
|
},
|
|
"primitive_delete": {
|
|
input: Value{
|
|
Before: "old",
|
|
},
|
|
attribute: &jsonprovider.Attribute{
|
|
AttributeType: []byte("\"string\""),
|
|
},
|
|
validateChange: change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
|
},
|
|
"primitive_update": {
|
|
input: Value{
|
|
Before: "old",
|
|
After: "new",
|
|
},
|
|
attribute: &jsonprovider.Attribute{
|
|
AttributeType: []byte("\"string\""),
|
|
},
|
|
validateChange: change.ValidatePrimitive(strptr("\"old\""), strptr("\"new\""), plans.Update, false),
|
|
},
|
|
"primitive_set_explicit_null": {
|
|
input: Value{
|
|
Before: "old",
|
|
After: nil,
|
|
AfterExplicit: true,
|
|
},
|
|
attribute: &jsonprovider.Attribute{
|
|
AttributeType: []byte("\"string\""),
|
|
},
|
|
validateChange: change.ValidatePrimitive(strptr("\"old\""), nil, plans.Update, false),
|
|
},
|
|
"primitive_unset_explicit_null": {
|
|
input: Value{
|
|
BeforeExplicit: true,
|
|
Before: nil,
|
|
After: "new",
|
|
},
|
|
attribute: &jsonprovider.Attribute{
|
|
AttributeType: []byte("\"string\""),
|
|
},
|
|
validateChange: change.ValidatePrimitive(nil, strptr("\"new\""), plans.Update, false),
|
|
},
|
|
"primitive_create_sensitive": {
|
|
input: Value{
|
|
Before: nil,
|
|
After: "new",
|
|
AfterSensitive: true,
|
|
},
|
|
attribute: &jsonprovider.Attribute{
|
|
AttributeType: []byte("\"string\""),
|
|
},
|
|
validateChange: change.ValidateSensitive(nil, "new", false, true, plans.Create, false),
|
|
},
|
|
"primitive_delete_sensitive": {
|
|
input: Value{
|
|
Before: "old",
|
|
BeforeSensitive: true,
|
|
After: nil,
|
|
},
|
|
attribute: &jsonprovider.Attribute{
|
|
AttributeType: []byte("\"string\""),
|
|
},
|
|
validateChange: change.ValidateSensitive("old", nil, true, false, plans.Delete, false),
|
|
},
|
|
"primitive_update_sensitive": {
|
|
input: Value{
|
|
Before: "old",
|
|
BeforeSensitive: true,
|
|
After: "new",
|
|
AfterSensitive: true,
|
|
},
|
|
attribute: &jsonprovider.Attribute{
|
|
AttributeType: []byte("\"string\""),
|
|
},
|
|
validateChange: change.ValidateSensitive("old", "new", true, true, plans.Update, false),
|
|
},
|
|
"primitive_create_computed": {
|
|
input: Value{
|
|
Before: nil,
|
|
After: nil,
|
|
Unknown: true,
|
|
},
|
|
attribute: &jsonprovider.Attribute{
|
|
AttributeType: []byte("\"string\""),
|
|
},
|
|
validateChange: change.ValidateComputed(nil, plans.Create, false),
|
|
},
|
|
"primitive_update_computed": {
|
|
input: Value{
|
|
Before: "old",
|
|
After: nil,
|
|
Unknown: true,
|
|
},
|
|
attribute: &jsonprovider.Attribute{
|
|
AttributeType: []byte("\"string\""),
|
|
},
|
|
validateChange: change.ValidateComputed(change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false), plans.Update, false),
|
|
},
|
|
}
|
|
for name, tc := range tcs {
|
|
t.Run(name, func(t *testing.T) {
|
|
tc.validateChange(t, tc.input.ComputeChange(tc.attribute))
|
|
})
|
|
}
|
|
}
|
|
|
|
func unmarshalType(t *testing.T, ctyType cty.Type) json.RawMessage {
|
|
msg, err := ctyjson.MarshalType(ctyType)
|
|
if err != nil {
|
|
t.Fatalf("invalid type: %s", ctyType.FriendlyName())
|
|
}
|
|
return msg
|
|
}
|