mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-25 01:00:16 -05:00
Ephemeral write only attributes (#3171)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org> Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
committed by
Christian Mesh
parent
cbe16d3a5d
commit
7f76707dd0
@@ -274,7 +274,6 @@ func TestEphemeralWorkflowAndOutput(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr)
|
||||
}
|
||||
// TODO ephemeral - this "value_wo" should be shown something like (write-only attribute). This will be handled during the work on the write-only attributes.
|
||||
expectedChangesOutput := `OpenTofu used the selected providers to generate the following execution
|
||||
plan. Resource actions are indicated with the following symbols:
|
||||
+ create
|
||||
@@ -285,18 +284,21 @@ OpenTofu will perform the following actions:
|
||||
# data.simple_resource.test_data2 will be read during apply
|
||||
# (depends on a resource or a module with changes pending)
|
||||
<= data "simple_resource" "test_data2" {
|
||||
+ id = (known after apply)
|
||||
+ value = "test"
|
||||
+ id = (known after apply)
|
||||
+ value = "test"
|
||||
+ value_wo = (write-only attribute)
|
||||
}
|
||||
|
||||
# simple_resource.test_res will be created
|
||||
+ resource "simple_resource" "test_res" {
|
||||
+ value = "test value"
|
||||
+ value = "test value"
|
||||
+ value_wo = (write-only attribute)
|
||||
}
|
||||
|
||||
# simple_resource.test_res_second_provider will be created
|
||||
+ resource "simple_resource" "test_res_second_provider" {
|
||||
+ value = "just a simple resource to ensure that the second provider it's working fine"
|
||||
+ value = "just a simple resource to ensure that the second provider it's working fine"
|
||||
+ value_wo = (write-only attribute)
|
||||
}
|
||||
|
||||
Plan: 2 to add, 0 to change, 0 to destroy.
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
variable "in" {
|
||||
type = string
|
||||
type = string
|
||||
description = "Variable that is marked as ephemeral and doesn't matter what value is given in, ephemeral or not, the value evaluated for this variable will be marked as ephemeral"
|
||||
ephemeral = true
|
||||
ephemeral = true
|
||||
}
|
||||
|
||||
output "out1" {
|
||||
value = var.in
|
||||
ephemeral = true // NOTE: because
|
||||
value = var.in
|
||||
// NOTE: because this output gets its value from referencing an ephemeral variable,
|
||||
// it needs to be configured as ephemeral too.
|
||||
ephemeral = true
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
package renderers
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"slices"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
@@ -42,6 +44,18 @@ func ValidatePrimitive(before, after interface{}, action plans.Action, replace b
|
||||
}
|
||||
}
|
||||
|
||||
func ValidateWriteOnly(action plans.Action, replace bool) ValidateDiffFunction {
|
||||
return func(t *testing.T, diff computed.Diff) {
|
||||
validateDiff(t, diff, action, replace)
|
||||
|
||||
_, ok := diff.Renderer.(*writeOnlyRenderer)
|
||||
if !ok {
|
||||
t.Errorf("invalid renderer type: %T", diff.Renderer)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ValidateObject(attributes map[string]ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction {
|
||||
return func(t *testing.T, diff computed.Diff) {
|
||||
validateDiff(t, diff, action, replace)
|
||||
@@ -121,6 +135,12 @@ func validateKeys[C, V any](t *testing.T, actual map[string]C, expected map[stri
|
||||
if diff := cmp.Diff(actualAttributes, expectedAttributes); len(diff) > 0 {
|
||||
t.Errorf("actual and expected attributes did not match: %s", diff)
|
||||
}
|
||||
} else {
|
||||
gotKeys := slices.Sorted(maps.Keys(actual))
|
||||
wantKeys := slices.Sorted(maps.Keys(expected))
|
||||
if diff := cmp.Diff(wantKeys, gotKeys); len(diff) > 0 {
|
||||
t.Errorf("keys not match: %s", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
24
internal/command/jsonformat/computed/renderers/write_only.go
Normal file
24
internal/command/jsonformat/computed/renderers/write_only.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) The OpenTofu Authors
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright (c) 2023 HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package renderers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/command/jsonformat/computed"
|
||||
)
|
||||
|
||||
func WriteOnly() computed.DiffRenderer {
|
||||
return &writeOnlyRenderer{}
|
||||
}
|
||||
|
||||
type writeOnlyRenderer struct {
|
||||
NoWarningsRenderer
|
||||
}
|
||||
|
||||
func (renderer writeOnlyRenderer) RenderHuman(diff computed.Diff, _ int, opts computed.RenderHumanOpts) string {
|
||||
return fmt.Sprintf("(write-only attribute)%s%s", forcesReplacement(diff.Replace, opts), nullSuffix(diff.Action, opts))
|
||||
}
|
||||
@@ -6,7 +6,9 @@
|
||||
package differ
|
||||
|
||||
import (
|
||||
"github.com/opentofu/opentofu/internal/command/jsonformat/computed/renderers"
|
||||
"github.com/opentofu/opentofu/internal/command/jsonformat/structured"
|
||||
"github.com/opentofu/opentofu/internal/plans"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
ctyjson "github.com/zclconf/go-cty/cty/json"
|
||||
|
||||
@@ -15,7 +17,20 @@ import (
|
||||
"github.com/opentofu/opentofu/internal/command/jsonprovider"
|
||||
)
|
||||
|
||||
func ComputeDiffForAttribute(change structured.Change, attribute *jsonprovider.Attribute) computed.Diff {
|
||||
// ComputeDiffForAttribute generates the diff for the change.
|
||||
// It handles 3 specific cases:
|
||||
// - When the attribute for which the change is generated is a nested object,
|
||||
// it generates the diff for each attribute
|
||||
// of the nested object.
|
||||
// - If the attribute is write-only, due to the fact that its changes will always be null, we want
|
||||
// to return a diff with the same action as the parent's.
|
||||
// If we use change.CalculateAction(), then the action will always be NoOp because of the
|
||||
// which will skip from showing this in the diff.
|
||||
// - If none above, it tries to generate the diff by using the specific generator for the attr type.
|
||||
func ComputeDiffForAttribute(change structured.Change, attribute *jsonprovider.Attribute, parentAction plans.Action) computed.Diff {
|
||||
if attribute.WriteOnly {
|
||||
return computeAttributeDiffAsWriteOnly(change, parentAction)
|
||||
}
|
||||
if attribute.AttributeNestedType != nil {
|
||||
return computeDiffForNestedAttribute(change, attribute.AttributeNestedType)
|
||||
}
|
||||
@@ -87,3 +102,7 @@ func unmarshalAttribute(attribute *jsonprovider.Attribute) cty.Type {
|
||||
}
|
||||
return ctyType
|
||||
}
|
||||
|
||||
func computeAttributeDiffAsWriteOnly(change structured.Change, parentAction plans.Action) computed.Diff {
|
||||
return asDiffWithInheritedAction(change, parentAction, renderers.WriteOnly())
|
||||
}
|
||||
|
||||
@@ -49,7 +49,11 @@ func ComputeDiffForBlock(change structured.Change, block *jsonprovider.Block) co
|
||||
childValue.BeforeExplicit = false
|
||||
childValue.AfterExplicit = false
|
||||
|
||||
childChange := ComputeDiffForAttribute(childValue, attr)
|
||||
// Because we want to print also the write-only attributes, we need to pass in the parent block
|
||||
// action instead of the child one.
|
||||
// This is because the child action will always result in NoOp since for write-only attributes, the
|
||||
// values returned will be null.
|
||||
childChange := ComputeDiffForAttribute(childValue, attr, current)
|
||||
if childChange.Action == plans.NoOp && childValue.Before == nil && childValue.After == nil {
|
||||
// Don't record nil values at all in blocks.
|
||||
continue
|
||||
|
||||
@@ -8,6 +8,7 @@ package differ
|
||||
import (
|
||||
"github.com/opentofu/opentofu/internal/command/jsonformat/computed"
|
||||
"github.com/opentofu/opentofu/internal/command/jsonformat/structured"
|
||||
"github.com/opentofu/opentofu/internal/plans"
|
||||
)
|
||||
|
||||
// asDiff is a helper function to abstract away some simple and common
|
||||
@@ -15,3 +16,10 @@ import (
|
||||
func asDiff(change structured.Change, renderer computed.DiffRenderer) computed.Diff {
|
||||
return computed.NewDiff(renderer, change.CalculateAction(), change.ReplacePaths.Matches())
|
||||
}
|
||||
|
||||
// asDiffWithInheritedAction is a specific implementation of asDiff that gets also a parentAction plans.Action.
|
||||
// This is used when the given change is known to always generate a NoOp diff, but it still should be shown
|
||||
// in the printed diff.
|
||||
func asDiffWithInheritedAction(change structured.Change, parentAction plans.Action, renderer computed.DiffRenderer) computed.Diff {
|
||||
return computed.NewDiff(renderer, parentAction, change.ReplacePaths.Matches())
|
||||
}
|
||||
|
||||
@@ -101,6 +101,71 @@ func TestValue_SimpleBlocks(t *testing.T) {
|
||||
"normal_attribute": renderers.ValidatePrimitive(nil, "some value", plans.Create, false),
|
||||
}, nil, nil, nil, nil, plans.Create, false),
|
||||
},
|
||||
|
||||
"delete_with_write_only_value": {
|
||||
input: structured.Change{
|
||||
Before: map[string]any{},
|
||||
After: nil,
|
||||
BeforeSensitive: false,
|
||||
AfterSensitive: false,
|
||||
},
|
||||
block: &jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"write_only_attribute": {
|
||||
AttributeType: unmarshalType(t, cty.String),
|
||||
WriteOnly: true,
|
||||
},
|
||||
},
|
||||
BlockTypes: map[string]*jsonprovider.BlockType{
|
||||
"nested_with_write_only": {
|
||||
NestingMode: "single",
|
||||
Block: &jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"inner_write_only": {
|
||||
AttributeType: unmarshalType(t, cty.String),
|
||||
WriteOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
validate: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{
|
||||
"write_only_attribute": renderers.ValidateWriteOnly(plans.Delete, false),
|
||||
}, nil, nil, nil, nil, plans.Delete, false),
|
||||
},
|
||||
"create_with_write_only_value": {
|
||||
input: structured.Change{
|
||||
Before: nil,
|
||||
After: map[string]any{},
|
||||
BeforeSensitive: false,
|
||||
AfterSensitive: false,
|
||||
},
|
||||
block: &jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"write_only_attribute": {
|
||||
AttributeType: unmarshalType(t, cty.String),
|
||||
WriteOnly: true,
|
||||
},
|
||||
},
|
||||
BlockTypes: map[string]*jsonprovider.BlockType{
|
||||
"nested_with_write_only": {
|
||||
NestingMode: "single",
|
||||
Block: &jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"inner_write_only": {
|
||||
AttributeType: unmarshalType(t, cty.String),
|
||||
WriteOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
validate: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{
|
||||
"write_only_attribute": renderers.ValidateWriteOnly(plans.Create, false),
|
||||
}, nil, nil, nil, nil, plans.Create, false),
|
||||
},
|
||||
}
|
||||
for name, tc := range tcs {
|
||||
// Set some default values
|
||||
@@ -746,17 +811,17 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
}
|
||||
|
||||
if tc.validateObject != nil {
|
||||
tc.validateObject(t, ComputeDiffForAttribute(tc.input, attribute))
|
||||
tc.validateObject(t, ComputeDiffForAttribute(tc.input, attribute, tc.input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
if tc.validateSingleDiff != nil {
|
||||
tc.validateSingleDiff(t, ComputeDiffForAttribute(tc.input, attribute))
|
||||
tc.validateSingleDiff(t, ComputeDiffForAttribute(tc.input, attribute, tc.input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
validate := renderers.ValidateObject(tc.validateDiffs, tc.validateAction, tc.validateReplace)
|
||||
validate(t, ComputeDiffForAttribute(tc.input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(tc.input, attribute, tc.input.CalculateAction()))
|
||||
})
|
||||
|
||||
t.Run("map", func(t *testing.T) {
|
||||
@@ -770,7 +835,7 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
validate := renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{
|
||||
"element": tc.validateObject,
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -778,14 +843,14 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
validate := renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{
|
||||
"element": tc.validateSingleDiff,
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
validate := renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{
|
||||
"element": renderers.ValidateObject(tc.validateDiffs, tc.validateAction, tc.validateReplace),
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
})
|
||||
|
||||
t.Run("list", func(t *testing.T) {
|
||||
@@ -796,7 +861,7 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
input := wrapChangeInSlice(tc.input)
|
||||
|
||||
if tc.validateList != nil {
|
||||
tc.validateList(t, ComputeDiffForAttribute(input, attribute))
|
||||
tc.validateList(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -804,7 +869,7 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
validate := renderers.ValidateList([]renderers.ValidateDiffFunction{
|
||||
tc.validateObject,
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -812,14 +877,14 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
validate := renderers.ValidateList([]renderers.ValidateDiffFunction{
|
||||
tc.validateSingleDiff,
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
validate := renderers.ValidateList([]renderers.ValidateDiffFunction{
|
||||
renderers.ValidateObject(tc.validateDiffs, tc.validateAction, tc.validateReplace),
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
})
|
||||
|
||||
t.Run("set", func(t *testing.T) {
|
||||
@@ -836,7 +901,7 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
ret = append(ret, tc.validateSetDiffs.After.Validate(renderers.ValidateObject))
|
||||
return ret
|
||||
}(), collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -844,7 +909,7 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
validate := renderers.ValidateSet([]renderers.ValidateDiffFunction{
|
||||
tc.validateObject,
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -852,14 +917,14 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
validate := renderers.ValidateSet([]renderers.ValidateDiffFunction{
|
||||
tc.validateSingleDiff,
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
validate := renderers.ValidateSet([]renderers.ValidateDiffFunction{
|
||||
renderers.ValidateObject(tc.validateDiffs, tc.validateAction, tc.validateReplace),
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -881,17 +946,17 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
}
|
||||
|
||||
if tc.validateNestedObject != nil {
|
||||
tc.validateNestedObject(t, ComputeDiffForAttribute(tc.input, attribute))
|
||||
tc.validateNestedObject(t, ComputeDiffForAttribute(tc.input, attribute, tc.input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
if tc.validateSingleDiff != nil {
|
||||
tc.validateSingleDiff(t, ComputeDiffForAttribute(tc.input, attribute))
|
||||
tc.validateSingleDiff(t, ComputeDiffForAttribute(tc.input, attribute, tc.input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
validate := renderers.ValidateNestedObject(tc.validateDiffs, tc.validateAction, tc.validateReplace)
|
||||
validate(t, ComputeDiffForAttribute(tc.input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(tc.input, attribute, tc.input.CalculateAction()))
|
||||
})
|
||||
|
||||
t.Run("map", func(t *testing.T) {
|
||||
@@ -916,7 +981,7 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
validate := renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{
|
||||
"element": tc.validateNestedObject,
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -924,14 +989,14 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
validate := renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{
|
||||
"element": tc.validateSingleDiff,
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
validate := renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{
|
||||
"element": renderers.ValidateNestedObject(tc.validateDiffs, tc.validateAction, tc.validateReplace),
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
})
|
||||
|
||||
t.Run("list", func(t *testing.T) {
|
||||
@@ -956,7 +1021,7 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
validate := renderers.ValidateNestedList([]renderers.ValidateDiffFunction{
|
||||
tc.validateNestedObject,
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -964,14 +1029,14 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
validate := renderers.ValidateNestedList([]renderers.ValidateDiffFunction{
|
||||
tc.validateSingleDiff,
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
validate := renderers.ValidateNestedList([]renderers.ValidateDiffFunction{
|
||||
renderers.ValidateNestedObject(tc.validateDiffs, tc.validateAction, tc.validateReplace),
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
})
|
||||
|
||||
t.Run("set", func(t *testing.T) {
|
||||
@@ -999,7 +1064,7 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
ret = append(ret, tc.validateSetDiffs.After.Validate(renderers.ValidateNestedObject))
|
||||
return ret
|
||||
}(), collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1007,7 +1072,7 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
validate := renderers.ValidateSet([]renderers.ValidateDiffFunction{
|
||||
tc.validateNestedObject,
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1015,14 +1080,14 @@ func TestValue_ObjectAttributes(t *testing.T) {
|
||||
validate := renderers.ValidateSet([]renderers.ValidateDiffFunction{
|
||||
tc.validateSingleDiff,
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
validate := renderers.ValidateSet([]renderers.ValidateDiffFunction{
|
||||
renderers.ValidateNestedObject(tc.validateDiffs, tc.validateAction, tc.validateReplace),
|
||||
}, collectionDefaultAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1840,9 +1905,9 @@ func TestValue_PrimitiveAttributes(t *testing.T) {
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Run("direct", func(t *testing.T) {
|
||||
tc.validateDiff(t, ComputeDiffForAttribute(tc.input, &jsonprovider.Attribute{
|
||||
AttributeType: unmarshalType(t, tc.attribute),
|
||||
}))
|
||||
attr := &jsonprovider.Attribute{AttributeType: unmarshalType(t, tc.attribute)}
|
||||
diff := ComputeDiffForAttribute(tc.input, attr, tc.input.CalculateAction())
|
||||
tc.validateDiff(t, diff)
|
||||
})
|
||||
|
||||
t.Run("map", func(t *testing.T) {
|
||||
@@ -1854,7 +1919,7 @@ func TestValue_PrimitiveAttributes(t *testing.T) {
|
||||
validate := renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{
|
||||
"element": tc.validateDiff,
|
||||
}, defaultCollectionsAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
})
|
||||
|
||||
t.Run("list", func(t *testing.T) {
|
||||
@@ -1865,14 +1930,14 @@ func TestValue_PrimitiveAttributes(t *testing.T) {
|
||||
|
||||
if tc.validateSliceDiffs != nil {
|
||||
validate := renderers.ValidateList(tc.validateSliceDiffs, defaultCollectionsAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
validate := renderers.ValidateList([]renderers.ValidateDiffFunction{
|
||||
tc.validateDiff,
|
||||
}, defaultCollectionsAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
})
|
||||
|
||||
t.Run("set", func(t *testing.T) {
|
||||
@@ -1883,14 +1948,14 @@ func TestValue_PrimitiveAttributes(t *testing.T) {
|
||||
|
||||
if tc.validateSliceDiffs != nil {
|
||||
validate := renderers.ValidateSet(tc.validateSliceDiffs, defaultCollectionsAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
return
|
||||
}
|
||||
|
||||
validate := renderers.ValidateSet([]renderers.ValidateDiffFunction{
|
||||
tc.validateDiff,
|
||||
}, defaultCollectionsAction, false)
|
||||
validate(t, ComputeDiffForAttribute(input, attribute))
|
||||
validate(t, ComputeDiffForAttribute(input, attribute, input.CalculateAction()))
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -2261,7 +2326,7 @@ func TestValue_CollectionAttributes(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc.validateDiff(t, ComputeDiffForAttribute(tc.input, tc.attribute))
|
||||
tc.validateDiff(t, ComputeDiffForAttribute(tc.input, tc.attribute, tc.input.CalculateAction()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3005,6 +3070,404 @@ func TestSpecificCases(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteOnly_ComputeDiffForBlock(t *testing.T) {
|
||||
// NOTE: The write-only change will *always* have the after change null.
|
||||
// We set specific values in this test for the after change to ensure that,
|
||||
// based on the attribute's schema, we use the correct renderers for these attributes.
|
||||
cases := map[string]struct {
|
||||
block jsonprovider.Block
|
||||
change structured.Change
|
||||
validator renderers.ValidateDiffFunction
|
||||
}{
|
||||
// ---> Attributes with basic types
|
||||
// attributes with basic types
|
||||
"write_only_primitive_attribute": {
|
||||
block: jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"write_only_attribute": {
|
||||
AttributeType: unmarshalType(t, cty.String),
|
||||
Optional: true,
|
||||
WriteOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
change: structured.Change{
|
||||
After: map[string]any{
|
||||
"write_only_attribute": "dummy value", // NOTE: since this is WO attr, IRL it's nil
|
||||
},
|
||||
},
|
||||
validator: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{
|
||||
"write_only_attribute": renderers.ValidateWriteOnly(plans.Create, false),
|
||||
}, nil, nil, nil, nil, plans.Create, false),
|
||||
},
|
||||
"write_only_list": {
|
||||
block: jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"write_only_list": {
|
||||
AttributeType: unmarshalType(t, cty.List(cty.Object(map[string]cty.Type{"val": cty.String}))),
|
||||
Optional: true,
|
||||
WriteOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
change: structured.Change{
|
||||
After: map[string]any{
|
||||
"write_only_list": []string{"val", "b"}, // NOTE: since this is WO attr, IRL it's nil
|
||||
},
|
||||
},
|
||||
validator: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{
|
||||
"write_only_list": renderers.ValidateWriteOnly(plans.Create, false),
|
||||
}, nil, nil, nil, nil, plans.Create, false),
|
||||
},
|
||||
"write_only_set": {
|
||||
block: jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"write_only_set": {
|
||||
AttributeType: unmarshalType(t, cty.Set(cty.Object(map[string]cty.Type{"val": cty.String}))),
|
||||
Optional: true,
|
||||
WriteOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
change: structured.Change{
|
||||
After: map[string]any{
|
||||
"write_only_set": []string{"val", "b"}, // NOTE: since this is WO attr, IRL it's nil
|
||||
},
|
||||
},
|
||||
validator: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{
|
||||
"write_only_set": renderers.ValidateWriteOnly(plans.Create, false),
|
||||
}, nil, nil, nil, nil, plans.Create, false),
|
||||
},
|
||||
"write_only_map": {
|
||||
block: jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"write_only_map": {
|
||||
AttributeType: unmarshalType(t, cty.Map(cty.String)),
|
||||
Optional: true,
|
||||
WriteOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
change: structured.Change{
|
||||
After: map[string]any{
|
||||
"write_only_map": map[string]string{"bar": "barv", "baz": "bazv"}, // NOTE: since this is WO attr, IRL it's nil
|
||||
},
|
||||
},
|
||||
validator: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{
|
||||
"write_only_map": renderers.ValidateWriteOnly(plans.Create, false),
|
||||
}, nil, nil, nil, nil, plans.Create, false),
|
||||
},
|
||||
"write_only_object": {
|
||||
block: jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"write_only_obj": {
|
||||
AttributeType: unmarshalType(t, cty.Object(map[string]cty.Type{"val": cty.String})),
|
||||
Optional: true,
|
||||
WriteOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
change: structured.Change{
|
||||
After: map[string]any{
|
||||
"write_only_obj": map[string]string{"bar": "barv"}, // NOTE: since this is WO attr, IRL it's nil
|
||||
},
|
||||
},
|
||||
validator: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{
|
||||
"write_only_obj": renderers.ValidateWriteOnly(plans.Create, false),
|
||||
}, nil, nil, nil, nil, plans.Create, false),
|
||||
},
|
||||
// ---> Attributes with nested types
|
||||
"attribute_single_nested_contains_write_only_attribute": {
|
||||
block: jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"write_only_obj": {
|
||||
AttributeNestedType: &jsonprovider.NestedType{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"val": {
|
||||
AttributeType: unmarshalType(t, cty.Object(map[string]cty.Type{"val": cty.String})),
|
||||
Optional: true,
|
||||
WriteOnly: true,
|
||||
},
|
||||
},
|
||||
NestingMode: "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
change: structured.Change{
|
||||
After: map[string]any{
|
||||
"write_only_obj": map[string]any{"val": "foo"}, // NOTE: since this is WO attr, IRL it's nil
|
||||
},
|
||||
},
|
||||
validator: renderers.ValidateBlock(
|
||||
map[string]renderers.ValidateDiffFunction{
|
||||
"write_only_obj": renderers.ValidateNestedObject(
|
||||
map[string]renderers.ValidateDiffFunction{
|
||||
"val": renderers.ValidateWriteOnly(plans.Create, false),
|
||||
},
|
||||
plans.Create, false),
|
||||
},
|
||||
nil, nil, nil, nil, plans.Create, false),
|
||||
},
|
||||
"attribute_map_nested_contains_write_only_attribute": {
|
||||
block: jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"write_only_map": {
|
||||
AttributeNestedType: &jsonprovider.NestedType{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"val": {
|
||||
AttributeType: unmarshalType(t, cty.Object(map[string]cty.Type{"val": cty.String})),
|
||||
Optional: true,
|
||||
WriteOnly: true,
|
||||
},
|
||||
},
|
||||
NestingMode: "map",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
change: structured.Change{
|
||||
After: map[string]any{
|
||||
"write_only_map": map[string]any{"val": "val value"}, // NOTE: since this is WO attr, IRL it's nil
|
||||
},
|
||||
},
|
||||
validator: renderers.ValidateBlock(
|
||||
map[string]renderers.ValidateDiffFunction{
|
||||
"write_only_map": renderers.ValidateMap(
|
||||
map[string]renderers.ValidateDiffFunction{
|
||||
"val": renderers.ValidateNestedObject(
|
||||
map[string]renderers.ValidateDiffFunction{
|
||||
"val": renderers.ValidateWriteOnly(plans.Create, false),
|
||||
},
|
||||
plans.Create, false),
|
||||
},
|
||||
plans.Create, false),
|
||||
},
|
||||
nil, nil, nil, nil, plans.Create, false),
|
||||
},
|
||||
"attribute_list_nested_contains_write_only_attribute": {
|
||||
block: jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"write_only_list": {
|
||||
AttributeNestedType: &jsonprovider.NestedType{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"val": {
|
||||
AttributeType: unmarshalType(t, cty.Object(map[string]cty.Type{"val": cty.String})),
|
||||
Optional: true,
|
||||
WriteOnly: true,
|
||||
},
|
||||
},
|
||||
NestingMode: "list",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
change: structured.Change{
|
||||
After: map[string]any{
|
||||
"write_only_list": []any{map[string]any{"val": "foo value"}}, // NOTE: since this is WO attr, IRL it's nil
|
||||
},
|
||||
},
|
||||
validator: renderers.ValidateBlock(
|
||||
map[string]renderers.ValidateDiffFunction{
|
||||
"write_only_list": renderers.ValidateNestedList(
|
||||
[]renderers.ValidateDiffFunction{
|
||||
renderers.ValidateNestedObject(
|
||||
map[string]renderers.ValidateDiffFunction{
|
||||
"val": renderers.ValidateWriteOnly(plans.Create, false),
|
||||
},
|
||||
plans.Create, false),
|
||||
},
|
||||
plans.Create, false),
|
||||
},
|
||||
nil, nil, nil, nil, plans.Create, false),
|
||||
},
|
||||
"attribute_set_nested_contains_write_only_attribute": {
|
||||
block: jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"write_only_set": {
|
||||
AttributeNestedType: &jsonprovider.NestedType{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"val": {
|
||||
AttributeType: unmarshalType(t, cty.Object(map[string]cty.Type{"val": cty.String})),
|
||||
Optional: true,
|
||||
WriteOnly: true,
|
||||
},
|
||||
},
|
||||
NestingMode: "set",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
change: structured.Change{
|
||||
After: map[string]any{
|
||||
"write_only_set": []any{map[string]any{"val": "foo value"}}, // NOTE: since this is WO attr, IRL it's nil
|
||||
},
|
||||
},
|
||||
validator: renderers.ValidateBlock(
|
||||
map[string]renderers.ValidateDiffFunction{
|
||||
"write_only_set": renderers.ValidateSet(
|
||||
[]renderers.ValidateDiffFunction{
|
||||
renderers.ValidateNestedObject(
|
||||
map[string]renderers.ValidateDiffFunction{
|
||||
"val": renderers.ValidateWriteOnly(plans.Create, false),
|
||||
},
|
||||
plans.Create, false),
|
||||
},
|
||||
plans.Create, false),
|
||||
},
|
||||
nil, nil, nil, nil, plans.Create, false),
|
||||
},
|
||||
// ---> Nested blocks
|
||||
"single_nested_block": {
|
||||
block: jsonprovider.Block{
|
||||
BlockTypes: map[string]*jsonprovider.BlockType{
|
||||
"nested_block": {
|
||||
NestingMode: "single",
|
||||
Block: &jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"write_only_attr": {
|
||||
AttributeType: unmarshalType(t, cty.String),
|
||||
WriteOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
change: structured.Change{
|
||||
After: map[string]any{
|
||||
"nested_block": map[string]any{"write_only_attr": "foo"}, // NOTE: since this is WO attr, IRL it's nil
|
||||
},
|
||||
},
|
||||
validator: renderers.ValidateBlock(
|
||||
nil,
|
||||
map[string]renderers.ValidateDiffFunction{
|
||||
"nested_block": renderers.ValidateBlock(
|
||||
map[string]renderers.ValidateDiffFunction{
|
||||
"write_only_attr": renderers.ValidateWriteOnly(plans.Create, false),
|
||||
},
|
||||
nil, nil, nil, nil, plans.Create, false),
|
||||
},
|
||||
nil, nil, nil, plans.Create, false),
|
||||
},
|
||||
"map_nested_block": {
|
||||
block: jsonprovider.Block{
|
||||
BlockTypes: map[string]*jsonprovider.BlockType{
|
||||
"nested_block": {
|
||||
NestingMode: "map",
|
||||
Block: &jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"write_only_attr": {
|
||||
AttributeType: unmarshalType(t, cty.String),
|
||||
WriteOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
change: structured.Change{
|
||||
After: map[string]any{
|
||||
"nested_block": map[string]any{"write_only_attr": "foo"}, // NOTE: since this is WO attr, IRL it's nil
|
||||
},
|
||||
},
|
||||
validator: renderers.ValidateBlock(
|
||||
nil, nil, nil,
|
||||
map[string]map[string]renderers.ValidateDiffFunction{
|
||||
"nested_block": {
|
||||
"write_only_attr": renderers.ValidateBlock(
|
||||
map[string]renderers.ValidateDiffFunction{
|
||||
"write_only_attr": renderers.ValidateWriteOnly(plans.Create, false),
|
||||
},
|
||||
nil, nil, nil, nil, plans.Create, false),
|
||||
},
|
||||
},
|
||||
nil, plans.Create, false),
|
||||
},
|
||||
"list_nested_block": {
|
||||
block: jsonprovider.Block{
|
||||
BlockTypes: map[string]*jsonprovider.BlockType{
|
||||
"nested_block": {
|
||||
NestingMode: "list",
|
||||
Block: &jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"write_only_attr": {
|
||||
AttributeType: unmarshalType(t, cty.String),
|
||||
WriteOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
change: structured.Change{
|
||||
After: map[string]any{
|
||||
"nested_block": []any{"value"}, // NOTE: since this is WO attr, IRL it's nil
|
||||
},
|
||||
},
|
||||
validator: renderers.ValidateBlock(
|
||||
nil, nil,
|
||||
map[string][]renderers.ValidateDiffFunction{
|
||||
"nested_block": {
|
||||
renderers.ValidateBlock(
|
||||
map[string]renderers.ValidateDiffFunction{
|
||||
"write_only_attr": renderers.ValidateWriteOnly(plans.Create, false),
|
||||
},
|
||||
nil, nil, nil, nil, plans.Create, false),
|
||||
},
|
||||
},
|
||||
nil, nil, plans.Create, false),
|
||||
},
|
||||
"set_nested_block": {
|
||||
block: jsonprovider.Block{
|
||||
BlockTypes: map[string]*jsonprovider.BlockType{
|
||||
"nested_block": {
|
||||
NestingMode: "set",
|
||||
Block: &jsonprovider.Block{
|
||||
Attributes: map[string]*jsonprovider.Attribute{
|
||||
"write_only_attr": {
|
||||
AttributeType: unmarshalType(t, cty.String),
|
||||
WriteOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
change: structured.Change{
|
||||
After: map[string]any{
|
||||
"nested_block": []any{"value"}, // NOTE: since this is WO attr, IRL it's nil
|
||||
},
|
||||
},
|
||||
validator: renderers.ValidateBlock(
|
||||
nil, nil, nil, nil,
|
||||
map[string][]renderers.ValidateDiffFunction{
|
||||
"nested_block": {
|
||||
renderers.ValidateBlock(
|
||||
map[string]renderers.ValidateDiffFunction{
|
||||
"write_only_attr": renderers.ValidateWriteOnly(plans.Create, false),
|
||||
},
|
||||
nil, nil, nil, nil, plans.Create, false),
|
||||
},
|
||||
}, plans.Create, false),
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tt.change.ReplacePaths == nil {
|
||||
tt.change.ReplacePaths = &attribute_path.PathMatcher{}
|
||||
}
|
||||
if tt.change.RelevantAttributes == nil {
|
||||
tt.change.RelevantAttributes = attribute_path.AlwaysMatcher()
|
||||
|
||||
}
|
||||
diff := ComputeDiffForBlock(tt.change, &tt.block)
|
||||
tt.validator(t, diff)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// unmarshalType converts a cty.Type into a json.RawMessage understood by the
|
||||
// schema. It also lets the testing framework handle any errors to keep the API
|
||||
// clean.
|
||||
|
||||
@@ -17,15 +17,15 @@ import (
|
||||
)
|
||||
|
||||
func computeAttributeDiffAsObject(change structured.Change, attributes map[string]cty.Type) computed.Diff {
|
||||
attributeDiffs, action := processObject(change, attributes, func(value structured.Change, ctype cty.Type) computed.Diff {
|
||||
attributeDiffs, action := processObject(change, attributes, func(value structured.Change, ctype cty.Type, _ plans.Action) computed.Diff {
|
||||
return ComputeDiffForType(value, ctype)
|
||||
})
|
||||
return computed.NewDiff(renderers.Object(attributeDiffs), action, change.ReplacePaths.Matches())
|
||||
}
|
||||
|
||||
func computeAttributeDiffAsNestedObject(change structured.Change, attributes map[string]*jsonprovider.Attribute) computed.Diff {
|
||||
attributeDiffs, action := processObject(change, attributes, func(value structured.Change, attribute *jsonprovider.Attribute) computed.Diff {
|
||||
return ComputeDiffForAttribute(value, attribute)
|
||||
attributeDiffs, action := processObject(change, attributes, func(value structured.Change, attribute *jsonprovider.Attribute, parentAction plans.Action) computed.Diff {
|
||||
return ComputeDiffForAttribute(value, attribute, parentAction)
|
||||
})
|
||||
return computed.NewDiff(renderers.NestedObject(attributeDiffs), action, change.ReplacePaths.Matches())
|
||||
}
|
||||
@@ -41,7 +41,7 @@ func computeAttributeDiffAsNestedObject(change structured.Change, attributes map
|
||||
// Also, as it generic we cannot make this function a method on Change as you
|
||||
// can't create generic methods on structs. Instead, we make this a generic
|
||||
// function that receives the value as an argument.
|
||||
func processObject[T any](v structured.Change, attributes map[string]T, computeDiff func(structured.Change, T) computed.Diff) (map[string]computed.Diff, plans.Action) {
|
||||
func processObject[T any](v structured.Change, attributes map[string]T, computeDiff func(structured.Change, T, plans.Action) computed.Diff) (map[string]computed.Diff, plans.Action) {
|
||||
attributeDiffs := make(map[string]computed.Diff)
|
||||
mapValue := v.AsMap()
|
||||
|
||||
@@ -58,7 +58,7 @@ func processObject[T any](v structured.Change, attributes map[string]T, computeD
|
||||
attributeValue.BeforeExplicit = false
|
||||
attributeValue.AfterExplicit = false
|
||||
|
||||
attributeDiff := computeDiff(attributeValue, attribute)
|
||||
attributeDiff := computeDiff(attributeValue, attribute, currentAction)
|
||||
if attributeDiff.Action == plans.NoOp && attributeValue.Before == nil && attributeValue.After == nil {
|
||||
// We skip attributes of objects that are null both before and
|
||||
// after. We don't even count these as unchanged attributes.
|
||||
|
||||
@@ -22,6 +22,7 @@ type Attribute struct {
|
||||
Optional bool `json:"optional,omitempty"`
|
||||
Computed bool `json:"computed,omitempty"`
|
||||
Sensitive bool `json:"sensitive,omitempty"`
|
||||
WriteOnly bool `json:"write_only,omitempty"`
|
||||
}
|
||||
|
||||
type NestedType struct {
|
||||
@@ -47,6 +48,7 @@ func marshalAttribute(attr *configschema.Attribute) *Attribute {
|
||||
Computed: attr.Computed,
|
||||
Sensitive: attr.Sensitive,
|
||||
Deprecated: attr.Deprecated,
|
||||
WriteOnly: attr.WriteOnly,
|
||||
}
|
||||
|
||||
// we're not concerned about errors because at this point the schema has
|
||||
|
||||
@@ -21,11 +21,13 @@ func TestMarshalAttribute(t *testing.T) {
|
||||
Want *Attribute
|
||||
}{
|
||||
{
|
||||
&configschema.Attribute{Type: cty.String, Optional: true, Computed: true},
|
||||
&configschema.Attribute{Type: cty.String, Optional: true, Computed: true, Sensitive: true, WriteOnly: true},
|
||||
&Attribute{
|
||||
AttributeType: json.RawMessage(`"string"`),
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Sensitive: true,
|
||||
WriteOnly: true,
|
||||
DescriptionKind: "plain",
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user