mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
plans/objchange: add handling of NestedTypes inside attributes
- rename ProposedNewObject to ProposedNew: Now that there is an actual configschema.Object it will be clearer if the function names match the type the act upon. - extract attribute-handling logic from assertPlanValid and extend A new function, assertPlannedAttrsValid, takes the existing functionality and extends it to validate attributes with NestedTypes. The NestedType-specific handling is in assertPlannedObjectValid, which is very similar to the block-handling logic, except that nulls are a valid plan (an attribute can be null, but not a block).
This commit is contained in:
parent
3ad720e9dc
commit
da6ac9d6cd
@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProposedNewObject constructs a proposed new object value by combining the
|
// ProposedNew constructs a proposed new object value by combining the
|
||||||
// computed attribute values from "prior" with the configured attribute values
|
// computed attribute values from "prior" with the configured attribute values
|
||||||
// from "config".
|
// from "config".
|
||||||
//
|
//
|
||||||
@ -24,7 +24,7 @@ import (
|
|||||||
// heuristic based on matching non-computed attribute values and so it may
|
// heuristic based on matching non-computed attribute values and so it may
|
||||||
// produce strange results with more "extreme" cases, such as a nested set
|
// produce strange results with more "extreme" cases, such as a nested set
|
||||||
// block where _all_ attributes are computed.
|
// block where _all_ attributes are computed.
|
||||||
func ProposedNewObject(schema *configschema.Block, prior, config cty.Value) cty.Value {
|
func ProposedNew(schema *configschema.Block, prior, config cty.Value) cty.Value {
|
||||||
// If the config and prior are both null, return early here before
|
// If the config and prior are both null, return early here before
|
||||||
// populating the prior block. The prevents non-null blocks from appearing
|
// populating the prior block. The prevents non-null blocks from appearing
|
||||||
// the proposed state value.
|
// the proposed state value.
|
||||||
@ -39,10 +39,10 @@ func ProposedNewObject(schema *configschema.Block, prior, config cty.Value) cty.
|
|||||||
// below by giving us one non-null level of object to pull values from.
|
// below by giving us one non-null level of object to pull values from.
|
||||||
prior = AllAttributesNull(schema)
|
prior = AllAttributesNull(schema)
|
||||||
}
|
}
|
||||||
return proposedNewObject(schema, prior, config)
|
return proposedNew(schema, prior, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlannedDataResourceObject is similar to ProposedNewObject but tailored for
|
// PlannedDataResourceObject is similar to proposedNewBlock but tailored for
|
||||||
// planning data resources in particular. Specifically, it replaces the values
|
// planning data resources in particular. Specifically, it replaces the values
|
||||||
// of any Computed attributes not set in the configuration with an unknown
|
// of any Computed attributes not set in the configuration with an unknown
|
||||||
// value, which serves as a placeholder for a value to be filled in by the
|
// value, which serves as a placeholder for a value to be filled in by the
|
||||||
@ -51,33 +51,32 @@ func ProposedNewObject(schema *configschema.Block, prior, config cty.Value) cty.
|
|||||||
// Data resources are different because the planning of them is handled
|
// Data resources are different because the planning of them is handled
|
||||||
// entirely within Terraform Core and not subject to customization by the
|
// entirely within Terraform Core and not subject to customization by the
|
||||||
// provider. This function is, in effect, producing an equivalent result to
|
// provider. This function is, in effect, producing an equivalent result to
|
||||||
// passing the ProposedNewObject result into a provider's PlanResourceChange
|
// passing the proposedNewBlock result into a provider's PlanResourceChange
|
||||||
// function, assuming a fixed implementation of PlanResourceChange that just
|
// function, assuming a fixed implementation of PlanResourceChange that just
|
||||||
// fills in unknown values as needed.
|
// fills in unknown values as needed.
|
||||||
func PlannedDataResourceObject(schema *configschema.Block, config cty.Value) cty.Value {
|
func PlannedDataResourceObject(schema *configschema.Block, config cty.Value) cty.Value {
|
||||||
// Our trick here is to run the ProposedNewObject logic with an
|
// Our trick here is to run the proposedNewBlock logic with an
|
||||||
// entirely-unknown prior value. Because of cty's unknown short-circuit
|
// entirely-unknown prior value. Because of cty's unknown short-circuit
|
||||||
// behavior, any operation on prior returns another unknown, and so
|
// behavior, any operation on prior returns another unknown, and so
|
||||||
// unknown values propagate into all of the parts of the resulting value
|
// unknown values propagate into all of the parts of the resulting value
|
||||||
// that would normally be filled in by preserving the prior state.
|
// that would normally be filled in by preserving the prior state.
|
||||||
prior := cty.UnknownVal(schema.ImpliedType())
|
prior := cty.UnknownVal(schema.ImpliedType())
|
||||||
return proposedNewObject(schema, prior, config)
|
return proposedNew(schema, prior, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func proposedNewObject(schema *configschema.Block, prior, config cty.Value) cty.Value {
|
func proposedNew(schema *configschema.Block, prior, config cty.Value) cty.Value {
|
||||||
if config.IsNull() || !config.IsKnown() {
|
if config.IsNull() || !config.IsKnown() {
|
||||||
// This is a weird situation, but we'll allow it anyway to free
|
// This is a weird situation, but we'll allow it anyway to free
|
||||||
// callers from needing to specifically check for these cases.
|
// callers from needing to specifically check for these cases.
|
||||||
return prior
|
return prior
|
||||||
}
|
}
|
||||||
if (!prior.Type().IsObjectType()) || (!config.Type().IsObjectType()) {
|
if (!prior.Type().IsObjectType()) || (!config.Type().IsObjectType()) {
|
||||||
panic("ProposedNewObject only supports object-typed values")
|
panic("ProposedNew only supports object-typed values")
|
||||||
}
|
}
|
||||||
|
|
||||||
// From this point onwards, we can assume that both values are non-null
|
// From this point onwards, we can assume that both values are non-null
|
||||||
// object types, and that the config value itself is known (though it
|
// object types, and that the config value itself is known (though it
|
||||||
// may contain nested values that are unknown.)
|
// may contain nested values that are unknown.)
|
||||||
|
|
||||||
newAttrs := map[string]cty.Value{}
|
newAttrs := map[string]cty.Value{}
|
||||||
for name, attr := range schema.Attributes {
|
for name, attr := range schema.Attributes {
|
||||||
priorV := prior.GetAttr(name)
|
priorV := prior.GetAttr(name)
|
||||||
@ -118,167 +117,171 @@ func proposedNewObject(schema *configschema.Block, prior, config cty.Value) cty.
|
|||||||
for name, blockType := range schema.BlockTypes {
|
for name, blockType := range schema.BlockTypes {
|
||||||
priorV := prior.GetAttr(name)
|
priorV := prior.GetAttr(name)
|
||||||
configV := config.GetAttr(name)
|
configV := config.GetAttr(name)
|
||||||
var newV cty.Value
|
newAttrs[name] = proposedNewNestedBlock(blockType, priorV, configV)
|
||||||
switch blockType.Nesting {
|
|
||||||
|
|
||||||
case configschema.NestingSingle, configschema.NestingGroup:
|
|
||||||
newV = ProposedNewObject(&blockType.Block, priorV, configV)
|
|
||||||
|
|
||||||
case configschema.NestingList:
|
|
||||||
// Nested blocks are correlated by index.
|
|
||||||
configVLen := 0
|
|
||||||
if configV.IsKnown() && !configV.IsNull() {
|
|
||||||
configVLen = configV.LengthInt()
|
|
||||||
}
|
|
||||||
if configVLen > 0 {
|
|
||||||
newVals := make([]cty.Value, 0, configVLen)
|
|
||||||
for it := configV.ElementIterator(); it.Next(); {
|
|
||||||
idx, configEV := it.Element()
|
|
||||||
if priorV.IsKnown() && (priorV.IsNull() || !priorV.HasIndex(idx).True()) {
|
|
||||||
// If there is no corresponding prior element then
|
|
||||||
// we just take the config value as-is.
|
|
||||||
newVals = append(newVals, configEV)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
priorEV := priorV.Index(idx)
|
|
||||||
|
|
||||||
newEV := ProposedNewObject(&blockType.Block, priorEV, configEV)
|
|
||||||
newVals = append(newVals, newEV)
|
|
||||||
}
|
|
||||||
// Despite the name, a NestingList might also be a tuple, if
|
|
||||||
// its nested schema contains dynamically-typed attributes.
|
|
||||||
if configV.Type().IsTupleType() {
|
|
||||||
newV = cty.TupleVal(newVals)
|
|
||||||
} else {
|
|
||||||
newV = cty.ListVal(newVals)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Despite the name, a NestingList might also be a tuple, if
|
|
||||||
// its nested schema contains dynamically-typed attributes.
|
|
||||||
if configV.Type().IsTupleType() {
|
|
||||||
newV = cty.EmptyTupleVal
|
|
||||||
} else {
|
|
||||||
newV = cty.ListValEmpty(blockType.ImpliedType())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case configschema.NestingMap:
|
|
||||||
// Despite the name, a NestingMap may produce either a map or
|
|
||||||
// object value, depending on whether the nested schema contains
|
|
||||||
// dynamically-typed attributes.
|
|
||||||
if configV.Type().IsObjectType() {
|
|
||||||
// Nested blocks are correlated by key.
|
|
||||||
configVLen := 0
|
|
||||||
if configV.IsKnown() && !configV.IsNull() {
|
|
||||||
configVLen = configV.LengthInt()
|
|
||||||
}
|
|
||||||
if configVLen > 0 {
|
|
||||||
newVals := make(map[string]cty.Value, configVLen)
|
|
||||||
atys := configV.Type().AttributeTypes()
|
|
||||||
for name := range atys {
|
|
||||||
configEV := configV.GetAttr(name)
|
|
||||||
if !priorV.IsKnown() || priorV.IsNull() || !priorV.Type().HasAttribute(name) {
|
|
||||||
// If there is no corresponding prior element then
|
|
||||||
// we just take the config value as-is.
|
|
||||||
newVals[name] = configEV
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
priorEV := priorV.GetAttr(name)
|
|
||||||
|
|
||||||
newEV := ProposedNewObject(&blockType.Block, priorEV, configEV)
|
|
||||||
newVals[name] = newEV
|
|
||||||
}
|
|
||||||
// Although we call the nesting mode "map", we actually use
|
|
||||||
// object values so that elements might have different types
|
|
||||||
// in case of dynamically-typed attributes.
|
|
||||||
newV = cty.ObjectVal(newVals)
|
|
||||||
} else {
|
|
||||||
newV = cty.EmptyObjectVal
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
configVLen := 0
|
|
||||||
if configV.IsKnown() && !configV.IsNull() {
|
|
||||||
configVLen = configV.LengthInt()
|
|
||||||
}
|
|
||||||
if configVLen > 0 {
|
|
||||||
newVals := make(map[string]cty.Value, configVLen)
|
|
||||||
for it := configV.ElementIterator(); it.Next(); {
|
|
||||||
idx, configEV := it.Element()
|
|
||||||
k := idx.AsString()
|
|
||||||
if priorV.IsKnown() && (priorV.IsNull() || !priorV.HasIndex(idx).True()) {
|
|
||||||
// If there is no corresponding prior element then
|
|
||||||
// we just take the config value as-is.
|
|
||||||
newVals[k] = configEV
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
priorEV := priorV.Index(idx)
|
|
||||||
|
|
||||||
newEV := ProposedNewObject(&blockType.Block, priorEV, configEV)
|
|
||||||
newVals[k] = newEV
|
|
||||||
}
|
|
||||||
newV = cty.MapVal(newVals)
|
|
||||||
} else {
|
|
||||||
newV = cty.MapValEmpty(blockType.ImpliedType())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case configschema.NestingSet:
|
|
||||||
if !configV.Type().IsSetType() {
|
|
||||||
panic("configschema.NestingSet value is not a set as expected")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nested blocks are correlated by comparing the element values
|
|
||||||
// after eliminating all of the computed attributes. In practice,
|
|
||||||
// this means that any config change produces an entirely new
|
|
||||||
// nested object, and we only propagate prior computed values
|
|
||||||
// if the non-computed attribute values are identical.
|
|
||||||
var cmpVals [][2]cty.Value
|
|
||||||
if priorV.IsKnown() && !priorV.IsNull() {
|
|
||||||
cmpVals = setElementCompareValues(&blockType.Block, priorV, false)
|
|
||||||
}
|
|
||||||
configVLen := 0
|
|
||||||
if configV.IsKnown() && !configV.IsNull() {
|
|
||||||
configVLen = configV.LengthInt()
|
|
||||||
}
|
|
||||||
if configVLen > 0 {
|
|
||||||
used := make([]bool, len(cmpVals)) // track used elements in case multiple have the same compare value
|
|
||||||
newVals := make([]cty.Value, 0, configVLen)
|
|
||||||
for it := configV.ElementIterator(); it.Next(); {
|
|
||||||
_, configEV := it.Element()
|
|
||||||
var priorEV cty.Value
|
|
||||||
for i, cmp := range cmpVals {
|
|
||||||
if used[i] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if cmp[1].RawEquals(configEV) {
|
|
||||||
priorEV = cmp[0]
|
|
||||||
used[i] = true // we can't use this value on a future iteration
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if priorEV == cty.NilVal {
|
|
||||||
priorEV = cty.NullVal(blockType.ImpliedType())
|
|
||||||
}
|
|
||||||
|
|
||||||
newEV := ProposedNewObject(&blockType.Block, priorEV, configEV)
|
|
||||||
newVals = append(newVals, newEV)
|
|
||||||
}
|
|
||||||
newV = cty.SetVal(newVals)
|
|
||||||
} else {
|
|
||||||
newV = cty.SetValEmpty(blockType.Block.ImpliedType())
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Should never happen, since the above cases are comprehensive.
|
|
||||||
panic(fmt.Sprintf("unsupported block nesting mode %s", blockType.Nesting))
|
|
||||||
}
|
|
||||||
|
|
||||||
newAttrs[name] = newV
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cty.ObjectVal(newAttrs)
|
return cty.ObjectVal(newAttrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func proposedNewNestedBlock(schema *configschema.NestedBlock, prior, config cty.Value) cty.Value {
|
||||||
|
var newV cty.Value
|
||||||
|
|
||||||
|
switch schema.Nesting {
|
||||||
|
|
||||||
|
case configschema.NestingSingle, configschema.NestingGroup:
|
||||||
|
newV = ProposedNew(&schema.Block, prior, config)
|
||||||
|
|
||||||
|
case configschema.NestingList:
|
||||||
|
// Nested blocks are correlated by index.
|
||||||
|
configVLen := 0
|
||||||
|
if config.IsKnown() && !config.IsNull() {
|
||||||
|
configVLen = config.LengthInt()
|
||||||
|
}
|
||||||
|
if configVLen > 0 {
|
||||||
|
newVals := make([]cty.Value, 0, configVLen)
|
||||||
|
for it := config.ElementIterator(); it.Next(); {
|
||||||
|
idx, configEV := it.Element()
|
||||||
|
if prior.IsKnown() && (prior.IsNull() || !prior.HasIndex(idx).True()) {
|
||||||
|
// If there is no corresponding prior element then
|
||||||
|
// we just take the config value as-is.
|
||||||
|
newVals = append(newVals, configEV)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
priorEV := prior.Index(idx)
|
||||||
|
|
||||||
|
newEV := ProposedNew(&schema.Block, priorEV, configEV)
|
||||||
|
newVals = append(newVals, newEV)
|
||||||
|
}
|
||||||
|
// Despite the name, a NestingList might also be a tuple, if
|
||||||
|
// its nested schema contains dynamically-typed attributes.
|
||||||
|
if config.Type().IsTupleType() {
|
||||||
|
newV = cty.TupleVal(newVals)
|
||||||
|
} else {
|
||||||
|
newV = cty.ListVal(newVals)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Despite the name, a NestingList might also be a tuple, if
|
||||||
|
// its nested schema contains dynamically-typed attributes.
|
||||||
|
if config.Type().IsTupleType() {
|
||||||
|
newV = cty.EmptyTupleVal
|
||||||
|
} else {
|
||||||
|
newV = cty.ListValEmpty(schema.ImpliedType())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case configschema.NestingMap:
|
||||||
|
// Despite the name, a NestingMap may produce either a map or
|
||||||
|
// object value, depending on whether the nested schema contains
|
||||||
|
// dynamically-typed attributes.
|
||||||
|
if config.Type().IsObjectType() {
|
||||||
|
// Nested blocks are correlated by key.
|
||||||
|
configVLen := 0
|
||||||
|
if config.IsKnown() && !config.IsNull() {
|
||||||
|
configVLen = config.LengthInt()
|
||||||
|
}
|
||||||
|
if configVLen > 0 {
|
||||||
|
newVals := make(map[string]cty.Value, configVLen)
|
||||||
|
atys := config.Type().AttributeTypes()
|
||||||
|
for name := range atys {
|
||||||
|
configEV := config.GetAttr(name)
|
||||||
|
if !prior.IsKnown() || prior.IsNull() || !prior.Type().HasAttribute(name) {
|
||||||
|
// If there is no corresponding prior element then
|
||||||
|
// we just take the config value as-is.
|
||||||
|
newVals[name] = configEV
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
priorEV := prior.GetAttr(name)
|
||||||
|
|
||||||
|
newEV := ProposedNew(&schema.Block, priorEV, configEV)
|
||||||
|
newVals[name] = newEV
|
||||||
|
}
|
||||||
|
// Although we call the nesting mode "map", we actually use
|
||||||
|
// object values so that elements might have different types
|
||||||
|
// in case of dynamically-typed attributes.
|
||||||
|
newV = cty.ObjectVal(newVals)
|
||||||
|
} else {
|
||||||
|
newV = cty.EmptyObjectVal
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
configVLen := 0
|
||||||
|
if config.IsKnown() && !config.IsNull() {
|
||||||
|
configVLen = config.LengthInt()
|
||||||
|
}
|
||||||
|
if configVLen > 0 {
|
||||||
|
newVals := make(map[string]cty.Value, configVLen)
|
||||||
|
for it := config.ElementIterator(); it.Next(); {
|
||||||
|
idx, configEV := it.Element()
|
||||||
|
k := idx.AsString()
|
||||||
|
if prior.IsKnown() && (prior.IsNull() || !prior.HasIndex(idx).True()) {
|
||||||
|
// If there is no corresponding prior element then
|
||||||
|
// we just take the config value as-is.
|
||||||
|
newVals[k] = configEV
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
priorEV := prior.Index(idx)
|
||||||
|
|
||||||
|
newEV := ProposedNew(&schema.Block, priorEV, configEV)
|
||||||
|
newVals[k] = newEV
|
||||||
|
}
|
||||||
|
newV = cty.MapVal(newVals)
|
||||||
|
} else {
|
||||||
|
newV = cty.MapValEmpty(schema.ImpliedType())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case configschema.NestingSet:
|
||||||
|
if !config.Type().IsSetType() {
|
||||||
|
panic("configschema.NestingSet value is not a set as expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nested blocks are correlated by comparing the element values
|
||||||
|
// after eliminating all of the computed attributes. In practice,
|
||||||
|
// this means that any config change produces an entirely new
|
||||||
|
// nested object, and we only propagate prior computed values
|
||||||
|
// if the non-computed attribute values are identical.
|
||||||
|
var cmpVals [][2]cty.Value
|
||||||
|
if prior.IsKnown() && !prior.IsNull() {
|
||||||
|
cmpVals = setElementCompareValues(&schema.Block, prior, false)
|
||||||
|
}
|
||||||
|
configVLen := 0
|
||||||
|
if config.IsKnown() && !config.IsNull() {
|
||||||
|
configVLen = config.LengthInt()
|
||||||
|
}
|
||||||
|
if configVLen > 0 {
|
||||||
|
used := make([]bool, len(cmpVals)) // track used elements in case multiple have the same compare value
|
||||||
|
newVals := make([]cty.Value, 0, configVLen)
|
||||||
|
for it := config.ElementIterator(); it.Next(); {
|
||||||
|
_, configEV := it.Element()
|
||||||
|
var priorEV cty.Value
|
||||||
|
for i, cmp := range cmpVals {
|
||||||
|
if used[i] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if cmp[1].RawEquals(configEV) {
|
||||||
|
priorEV = cmp[0]
|
||||||
|
used[i] = true // we can't use this value on a future iteration
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if priorEV == cty.NilVal {
|
||||||
|
priorEV = cty.NullVal(schema.ImpliedType())
|
||||||
|
}
|
||||||
|
|
||||||
|
newEV := ProposedNew(&schema.Block, priorEV, configEV)
|
||||||
|
newVals = append(newVals, newEV)
|
||||||
|
}
|
||||||
|
newV = cty.SetVal(newVals)
|
||||||
|
} else {
|
||||||
|
newV = cty.SetValEmpty(schema.Block.ImpliedType())
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Should never happen, since the above cases are comprehensive.
|
||||||
|
panic(fmt.Sprintf("unsupported block nesting mode %s", schema.Nesting))
|
||||||
|
}
|
||||||
|
return newV
|
||||||
|
}
|
||||||
|
|
||||||
// setElementCompareValues takes a known, non-null value of a cty.Set type and
|
// setElementCompareValues takes a known, non-null value of a cty.Set type and
|
||||||
// returns a table -- constructed of two-element arrays -- that maps original
|
// returns a table -- constructed of two-element arrays -- that maps original
|
||||||
// set element values to corresponding values that have all of the computed
|
// set element values to corresponding values that have all of the computed
|
||||||
@ -290,7 +293,7 @@ func proposedNewObject(schema *configschema.Block, prior, config cty.Value) cty.
|
|||||||
// value and the one-indexed element is the corresponding "compare value".
|
// value and the one-indexed element is the corresponding "compare value".
|
||||||
//
|
//
|
||||||
// This is intended to help correlate prior elements with configured elements
|
// This is intended to help correlate prior elements with configured elements
|
||||||
// in ProposedNewObject. The result is a heuristic rather than an exact science,
|
// in proposedNewBlock. The result is a heuristic rather than an exact science,
|
||||||
// since e.g. two separate elements may reduce to the same value through this
|
// since e.g. two separate elements may reduce to the same value through this
|
||||||
// process. The caller must therefore be ready to deal with duplicates.
|
// process. The caller must therefore be ready to deal with duplicates.
|
||||||
func setElementCompareValues(schema *configschema.Block, set cty.Value, isConfig bool) [][2]cty.Value {
|
func setElementCompareValues(schema *configschema.Block, set cty.Value, isConfig bool) [][2]cty.Value {
|
||||||
|
@ -33,6 +33,18 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
Type: cty.String,
|
Type: cty.String,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
},
|
},
|
||||||
|
"bloop": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingSingle,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"blop": {
|
||||||
|
Type: cty.String,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
BlockTypes: map[string]*configschema.NestedBlock{
|
BlockTypes: map[string]*configschema.NestedBlock{
|
||||||
"baz": {
|
"baz": {
|
||||||
@ -57,6 +69,9 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
cty.NullVal(cty.DynamicPseudoType),
|
cty.NullVal(cty.DynamicPseudoType),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.StringVal("hello"),
|
"foo": cty.StringVal("hello"),
|
||||||
|
"bloop": cty.NullVal(cty.Object(map[string]cty.Type{
|
||||||
|
"blop": cty.String,
|
||||||
|
})),
|
||||||
"bar": cty.NullVal(cty.String),
|
"bar": cty.NullVal(cty.String),
|
||||||
"baz": cty.ObjectVal(map[string]cty.Value{
|
"baz": cty.ObjectVal(map[string]cty.Value{
|
||||||
"boz": cty.StringVal("world"),
|
"boz": cty.StringVal("world"),
|
||||||
@ -76,6 +91,9 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
// usually changes them to "unknown" during PlanResourceChange,
|
// usually changes them to "unknown" during PlanResourceChange,
|
||||||
// to indicate that the value will be decided during apply.
|
// to indicate that the value will be decided during apply.
|
||||||
"bar": cty.NullVal(cty.String),
|
"bar": cty.NullVal(cty.String),
|
||||||
|
"bloop": cty.NullVal(cty.Object(map[string]cty.Type{
|
||||||
|
"blop": cty.String,
|
||||||
|
})),
|
||||||
|
|
||||||
"baz": cty.ObjectVal(map[string]cty.Value{
|
"baz": cty.ObjectVal(map[string]cty.Value{
|
||||||
"boz": cty.StringVal("world"),
|
"boz": cty.StringVal("world"),
|
||||||
@ -90,6 +108,18 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
Type: cty.String,
|
Type: cty.String,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
"bloop": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingSingle,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"blop": {
|
||||||
|
Type: cty.String,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
BlockTypes: map[string]*configschema.NestedBlock{
|
BlockTypes: map[string]*configschema.NestedBlock{
|
||||||
"baz": {
|
"baz": {
|
||||||
@ -109,14 +139,20 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
cty.NullVal(cty.DynamicPseudoType),
|
cty.NullVal(cty.DynamicPseudoType),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.StringVal("bar"),
|
"foo": cty.StringVal("bar"),
|
||||||
|
"bloop": cty.NullVal(cty.Object(map[string]cty.Type{
|
||||||
|
"blop": cty.String,
|
||||||
|
})),
|
||||||
"baz": cty.NullVal(cty.Object(map[string]cty.Type{
|
"baz": cty.NullVal(cty.Object(map[string]cty.Type{
|
||||||
"boz": cty.String,
|
"boz": cty.String,
|
||||||
})),
|
})),
|
||||||
}),
|
}),
|
||||||
// The baz block does not exist in the config, and therefore
|
// The bloop attribue and baz block does not exist in the config,
|
||||||
// shouldn't be planned.
|
// and therefore shouldn't be planned.
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.StringVal("bar"),
|
"foo": cty.StringVal("bar"),
|
||||||
|
"bloop": cty.NullVal(cty.Object(map[string]cty.Type{
|
||||||
|
"blop": cty.String,
|
||||||
|
})),
|
||||||
"baz": cty.NullVal(cty.Object(map[string]cty.Type{
|
"baz": cty.NullVal(cty.Object(map[string]cty.Type{
|
||||||
"boz": cty.String,
|
"boz": cty.String,
|
||||||
})),
|
})),
|
||||||
@ -141,6 +177,21 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bloop": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingSet,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"blop": {
|
||||||
|
Type: cty.String,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Computed: true,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
cty.NullVal(cty.DynamicPseudoType),
|
cty.NullVal(cty.DynamicPseudoType),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
@ -149,6 +200,11 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"boz": cty.StringVal("world"),
|
"boz": cty.StringVal("world"),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.SetVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("blub"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"baz": cty.SetVal([]cty.Value{
|
"baz": cty.SetVal([]cty.Value{
|
||||||
@ -156,6 +212,11 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"boz": cty.StringVal("world"),
|
"boz": cty.StringVal("world"),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.SetVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("blub"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
"prior attributes": {
|
"prior attributes": {
|
||||||
@ -179,6 +240,18 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
},
|
},
|
||||||
|
"bloop": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingSingle,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"blop": {
|
||||||
|
Type: cty.String,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
@ -186,18 +259,27 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"bar": cty.StringVal("petit dejeuner"),
|
"bar": cty.StringVal("petit dejeuner"),
|
||||||
"baz": cty.StringVal("grande dejeuner"),
|
"baz": cty.StringVal("grande dejeuner"),
|
||||||
"boz": cty.StringVal("a la monde"),
|
"boz": cty.StringVal("a la monde"),
|
||||||
|
"bloop": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("glub"),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.StringVal("hello"),
|
"foo": cty.StringVal("hello"),
|
||||||
"bar": cty.NullVal(cty.String),
|
"bar": cty.NullVal(cty.String),
|
||||||
"baz": cty.NullVal(cty.String),
|
"baz": cty.NullVal(cty.String),
|
||||||
"boz": cty.StringVal("world"),
|
"boz": cty.StringVal("world"),
|
||||||
|
"bloop": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("bleep"),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.StringVal("hello"),
|
"foo": cty.StringVal("hello"),
|
||||||
"bar": cty.StringVal("petit dejeuner"),
|
"bar": cty.StringVal("petit dejeuner"),
|
||||||
"baz": cty.StringVal("grande dejeuner"),
|
"baz": cty.StringVal("grande dejeuner"),
|
||||||
"boz": cty.StringVal("world"),
|
"boz": cty.StringVal("world"),
|
||||||
|
"bloop": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("bleep"),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
"prior nested single": {
|
"prior nested single": {
|
||||||
@ -221,24 +303,54 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bloop": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingSingle,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"blop": {
|
||||||
|
Type: cty.String,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"bleep": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.ObjectVal(map[string]cty.Value{
|
"foo": cty.ObjectVal(map[string]cty.Value{
|
||||||
"bar": cty.StringVal("beep"),
|
"bar": cty.StringVal("beep"),
|
||||||
"baz": cty.StringVal("boop"),
|
"baz": cty.StringVal("boop"),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("glub"),
|
||||||
|
"bleep": cty.NullVal(cty.String),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.ObjectVal(map[string]cty.Value{
|
"foo": cty.ObjectVal(map[string]cty.Value{
|
||||||
"bar": cty.StringVal("bap"),
|
"bar": cty.StringVal("bap"),
|
||||||
"baz": cty.NullVal(cty.String),
|
"baz": cty.NullVal(cty.String),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("glub"),
|
||||||
|
"bleep": cty.StringVal("beep"),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.ObjectVal(map[string]cty.Value{
|
"foo": cty.ObjectVal(map[string]cty.Value{
|
||||||
"bar": cty.StringVal("bap"),
|
"bar": cty.StringVal("bap"),
|
||||||
"baz": cty.StringVal("boop"),
|
"baz": cty.StringVal("boop"),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("glub"),
|
||||||
|
"bleep": cty.StringVal("beep"),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
"prior nested list": {
|
"prior nested list": {
|
||||||
@ -262,6 +374,20 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bloop": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingList,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"blop": {
|
||||||
|
Type: cty.String,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.ListVal([]cty.Value{
|
"foo": cty.ListVal([]cty.Value{
|
||||||
@ -270,6 +396,14 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"baz": cty.StringVal("boop"),
|
"baz": cty.StringVal("boop"),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("bar"),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("baz"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.ListVal([]cty.Value{
|
"foo": cty.ListVal([]cty.Value{
|
||||||
@ -282,6 +416,14 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"baz": cty.NullVal(cty.String),
|
"baz": cty.NullVal(cty.String),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("bar"),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("baz"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.ListVal([]cty.Value{
|
"foo": cty.ListVal([]cty.Value{
|
||||||
@ -294,6 +436,14 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"baz": cty.NullVal(cty.String),
|
"baz": cty.NullVal(cty.String),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("bar"),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("baz"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
"prior nested list with dynamic": {
|
"prior nested list with dynamic": {
|
||||||
@ -317,6 +467,24 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bloop": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingList,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"blop": {
|
||||||
|
Type: cty.DynamicPseudoType,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"blub": {
|
||||||
|
Type: cty.DynamicPseudoType,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.TupleVal([]cty.Value{
|
"foo": cty.TupleVal([]cty.Value{
|
||||||
@ -325,6 +493,16 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"baz": cty.StringVal("boop"),
|
"baz": cty.StringVal("boop"),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("bar"),
|
||||||
|
"blub": cty.StringVal("glub"),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("baz"),
|
||||||
|
"blub": cty.NullVal(cty.String),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.TupleVal([]cty.Value{
|
"foo": cty.TupleVal([]cty.Value{
|
||||||
@ -337,6 +515,12 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"baz": cty.NullVal(cty.String),
|
"baz": cty.NullVal(cty.String),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("bar"),
|
||||||
|
"blub": cty.NullVal(cty.String),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.TupleVal([]cty.Value{
|
"foo": cty.TupleVal([]cty.Value{
|
||||||
@ -349,6 +533,12 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"baz": cty.NullVal(cty.String),
|
"baz": cty.NullVal(cty.String),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("bar"),
|
||||||
|
"blub": cty.NullVal(cty.String),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
"prior nested map": {
|
"prior nested map": {
|
||||||
@ -372,6 +562,20 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bloop": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingMap,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"blop": {
|
||||||
|
Type: cty.String,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.MapVal(map[string]cty.Value{
|
"foo": cty.MapVal(map[string]cty.Value{
|
||||||
@ -384,6 +588,14 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"baz": cty.StringVal("boot"),
|
"baz": cty.StringVal("boot"),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.MapVal(map[string]cty.Value{
|
||||||
|
"a": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("glub"),
|
||||||
|
}),
|
||||||
|
"b": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("blub"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.MapVal(map[string]cty.Value{
|
"foo": cty.MapVal(map[string]cty.Value{
|
||||||
@ -396,6 +608,14 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"baz": cty.NullVal(cty.String),
|
"baz": cty.NullVal(cty.String),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.MapVal(map[string]cty.Value{
|
||||||
|
"a": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("glub"),
|
||||||
|
}),
|
||||||
|
"c": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("blub"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.MapVal(map[string]cty.Value{
|
"foo": cty.MapVal(map[string]cty.Value{
|
||||||
@ -408,6 +628,14 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"baz": cty.NullVal(cty.String),
|
"baz": cty.NullVal(cty.String),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.MapVal(map[string]cty.Value{
|
||||||
|
"a": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("glub"),
|
||||||
|
}),
|
||||||
|
"c": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("blub"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
"prior nested map with dynamic": {
|
"prior nested map with dynamic": {
|
||||||
@ -431,6 +659,20 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bloop": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingMap,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"blop": {
|
||||||
|
Type: cty.DynamicPseudoType,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.ObjectVal(map[string]cty.Value{
|
"foo": cty.ObjectVal(map[string]cty.Value{
|
||||||
@ -443,6 +685,14 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"baz": cty.ListVal([]cty.Value{cty.StringVal("boot")}),
|
"baz": cty.ListVal([]cty.Value{cty.StringVal("boot")}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("glub"),
|
||||||
|
}),
|
||||||
|
"b": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.NumberIntVal(13),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.ObjectVal(map[string]cty.Value{
|
"foo": cty.ObjectVal(map[string]cty.Value{
|
||||||
@ -455,6 +705,14 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"baz": cty.NullVal(cty.List(cty.String)),
|
"baz": cty.NullVal(cty.List(cty.String)),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("blep"),
|
||||||
|
}),
|
||||||
|
"c": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.NumberIntVal(13),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.ObjectVal(map[string]cty.Value{
|
"foo": cty.ObjectVal(map[string]cty.Value{
|
||||||
@ -467,6 +725,14 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"baz": cty.NullVal(cty.List(cty.String)),
|
"baz": cty.NullVal(cty.List(cty.String)),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("blep"),
|
||||||
|
}),
|
||||||
|
"c": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.NumberIntVal(13),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
"prior nested set": {
|
"prior nested set": {
|
||||||
@ -492,6 +758,24 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bloop": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingSet,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"blop": {
|
||||||
|
Type: cty.String,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"bleep": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.SetVal([]cty.Value{
|
"foo": cty.SetVal([]cty.Value{
|
||||||
@ -504,6 +788,16 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"baz": cty.StringVal("boot"),
|
"baz": cty.StringVal("boot"),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.SetVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("glubglub"),
|
||||||
|
"bleep": cty.NullVal(cty.String),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("glubglub"),
|
||||||
|
"bleep": cty.StringVal("beep"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.SetVal([]cty.Value{
|
"foo": cty.SetVal([]cty.Value{
|
||||||
@ -516,6 +810,16 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"baz": cty.NullVal(cty.String),
|
"baz": cty.NullVal(cty.String),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.SetVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("glubglub"),
|
||||||
|
"bleep": cty.NullVal(cty.String),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("glub"),
|
||||||
|
"bleep": cty.NullVal(cty.String),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.SetVal([]cty.Value{
|
"foo": cty.SetVal([]cty.Value{
|
||||||
@ -528,6 +832,16 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"baz": cty.NullVal(cty.String),
|
"baz": cty.NullVal(cty.String),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.SetVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("glubglub"),
|
||||||
|
"bleep": cty.NullVal(cty.String),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("glub"),
|
||||||
|
"bleep": cty.NullVal(cty.String),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
"sets differing only by unknown": {
|
"sets differing only by unknown": {
|
||||||
@ -546,6 +860,20 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bloop": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingSet,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"blop": {
|
||||||
|
Type: cty.String,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
cty.NullVal(cty.DynamicPseudoType),
|
cty.NullVal(cty.DynamicPseudoType),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
@ -557,6 +885,14 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"optional": cty.UnknownVal(cty.String),
|
"optional": cty.UnknownVal(cty.String),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.SetVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.UnknownVal(cty.String),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.UnknownVal(cty.String),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"multi": cty.SetVal([]cty.Value{
|
"multi": cty.SetVal([]cty.Value{
|
||||||
@ -570,6 +906,14 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
"optional": cty.UnknownVal(cty.String),
|
"optional": cty.UnknownVal(cty.String),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
"bloop": cty.SetVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.UnknownVal(cty.String),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.UnknownVal(cty.String),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
"nested list in set": {
|
"nested list in set": {
|
||||||
@ -858,7 +1202,7 @@ func TestProposedNewObject(t *testing.T) {
|
|||||||
|
|
||||||
for name, test := range tests {
|
for name, test := range tests {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
got := ProposedNewObject(test.Schema, test.Prior, test.Config)
|
got := ProposedNew(test.Schema, test.Prior, test.Config)
|
||||||
if !got.RawEquals(test.Want) {
|
if !got.RawEquals(test.Want) {
|
||||||
t.Errorf("wrong result\ngot: %swant: %s", dump.Value(got), dump.Value(test.Want))
|
t.Errorf("wrong result\ngot: %swant: %s", dump.Value(got), dump.Value(test.Want))
|
||||||
}
|
}
|
||||||
|
@ -53,18 +53,10 @@ func assertPlanValid(schema *configschema.Block, priorState, config, plannedStat
|
|||||||
|
|
||||||
impTy := schema.ImpliedType()
|
impTy := schema.ImpliedType()
|
||||||
|
|
||||||
for name, attrS := range schema.Attributes {
|
// verify attributes
|
||||||
plannedV := plannedState.GetAttr(name)
|
moreErrs := assertPlannedAttrsValid(schema.Attributes, priorState, config, plannedState, path)
|
||||||
configV := config.GetAttr(name)
|
errs = append(errs, moreErrs...)
|
||||||
priorV := cty.NullVal(attrS.Type)
|
|
||||||
if !priorState.IsNull() {
|
|
||||||
priorV = priorState.GetAttr(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
path := append(path, cty.GetAttrStep{Name: name})
|
|
||||||
moreErrs := assertPlannedValueValid(attrS, priorV, configV, plannedV, path)
|
|
||||||
errs = append(errs, moreErrs...)
|
|
||||||
}
|
|
||||||
for name, blockS := range schema.BlockTypes {
|
for name, blockS := range schema.BlockTypes {
|
||||||
path := append(path, cty.GetAttrStep{Name: name})
|
path := append(path, cty.GetAttrStep{Name: name})
|
||||||
plannedV := plannedState.GetAttr(name)
|
plannedV := plannedState.GetAttr(name)
|
||||||
@ -229,13 +221,34 @@ func assertPlanValid(schema *configschema.Block, priorState, config, plannedStat
|
|||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertPlannedAttrsValid(schema map[string]*configschema.Attribute, priorState, config, plannedState cty.Value, path cty.Path) []error {
|
||||||
|
var errs []error
|
||||||
|
for name, attrS := range schema {
|
||||||
|
moreErrs := assertPlannedAttrValid(name, attrS, priorState, config, plannedState, path)
|
||||||
|
errs = append(errs, moreErrs...)
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertPlannedAttrValid(name string, attrS *configschema.Attribute, priorState, config, plannedState cty.Value, path cty.Path) []error {
|
||||||
|
plannedV := plannedState.GetAttr(name)
|
||||||
|
configV := config.GetAttr(name)
|
||||||
|
priorV := cty.NullVal(attrS.Type)
|
||||||
|
if !priorState.IsNull() {
|
||||||
|
priorV = priorState.GetAttr(name)
|
||||||
|
}
|
||||||
|
path = append(path, cty.GetAttrStep{Name: name})
|
||||||
|
|
||||||
|
return assertPlannedValueValid(attrS, priorV, configV, plannedV, path)
|
||||||
|
}
|
||||||
|
|
||||||
func assertPlannedValueValid(attrS *configschema.Attribute, priorV, configV, plannedV cty.Value, path cty.Path) []error {
|
func assertPlannedValueValid(attrS *configschema.Attribute, priorV, configV, plannedV cty.Value, path cty.Path) []error {
|
||||||
var errs []error
|
var errs []error
|
||||||
if plannedV.RawEquals(configV) {
|
if plannedV.RawEquals(configV) {
|
||||||
// This is the easy path: provider didn't change anything at all.
|
// This is the easy path: provider didn't change anything at all.
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
if plannedV.RawEquals(priorV) && !priorV.IsNull() {
|
if plannedV.RawEquals(priorV) && !priorV.IsNull() && !configV.IsNull() {
|
||||||
// Also pretty easy: there is a prior value and the provider has
|
// Also pretty easy: there is a prior value and the provider has
|
||||||
// returned it unchanged. This indicates that configV and plannedV
|
// returned it unchanged. This indicates that configV and plannedV
|
||||||
// are functionally equivalent and so the provider wishes to disregard
|
// are functionally equivalent and so the provider wishes to disregard
|
||||||
@ -248,6 +261,11 @@ func assertPlannedValueValid(attrS *configschema.Attribute, priorV, configV, pla
|
|||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this attribute has a NestedType, validate the nested object
|
||||||
|
if attrS.NestedType != nil {
|
||||||
|
return assertPlannedObjectValid(attrS.NestedType, priorV, configV, plannedV, path)
|
||||||
|
}
|
||||||
|
|
||||||
// If none of the above conditions match, the provider has made an invalid
|
// If none of the above conditions match, the provider has made an invalid
|
||||||
// change to this attribute.
|
// change to this attribute.
|
||||||
if priorV.IsNull() {
|
if priorV.IsNull() {
|
||||||
@ -265,3 +283,151 @@ func assertPlannedValueValid(attrS *configschema.Attribute, priorV, configV, pla
|
|||||||
}
|
}
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertPlannedObjectValid(schema *configschema.Object, prior, config, planned cty.Value, path cty.Path) []error {
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
if planned.IsNull() && !config.IsNull() {
|
||||||
|
errs = append(errs, path.NewErrorf("planned for absense but config wants existence"))
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
if config.IsNull() && !planned.IsNull() {
|
||||||
|
errs = append(errs, path.NewErrorf("planned for existence but config wants absense"))
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
if planned.IsNull() {
|
||||||
|
// No further checks possible if the planned value is null
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
switch schema.Nesting {
|
||||||
|
case configschema.NestingSingle, configschema.NestingGroup:
|
||||||
|
moreErrs := assertPlannedAttrsValid(schema.Attributes, prior, config, planned, path)
|
||||||
|
errs = append(errs, moreErrs...)
|
||||||
|
|
||||||
|
case configschema.NestingList:
|
||||||
|
// A NestingList might either be a list or a tuple, depending on
|
||||||
|
// whether there are dynamically-typed attributes inside. However,
|
||||||
|
// both support a similar-enough API that we can treat them the
|
||||||
|
// same for our purposes here.
|
||||||
|
|
||||||
|
plannedL := planned.LengthInt()
|
||||||
|
configL := config.LengthInt()
|
||||||
|
if plannedL != configL {
|
||||||
|
errs = append(errs, path.NewErrorf("count in plan (%d) disagrees with count in config (%d)", plannedL, configL))
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
for it := planned.ElementIterator(); it.Next(); {
|
||||||
|
idx, plannedEV := it.Element()
|
||||||
|
path := append(path, cty.IndexStep{Key: idx})
|
||||||
|
if !plannedEV.IsKnown() {
|
||||||
|
errs = append(errs, path.NewErrorf("element representing nested block must not be unknown itself; set nested attribute values to unknown instead"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !config.HasIndex(idx).True() {
|
||||||
|
continue // should never happen since we checked the lengths above
|
||||||
|
}
|
||||||
|
configEV := config.Index(idx)
|
||||||
|
priorEV := cty.NullVal(schema.ImpliedType())
|
||||||
|
if !prior.IsNull() && prior.HasIndex(idx).True() {
|
||||||
|
priorEV = prior.Index(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
moreErrs := assertPlannedAttrsValid(schema.Attributes, priorEV, configEV, plannedEV, path)
|
||||||
|
errs = append(errs, moreErrs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
case configschema.NestingMap:
|
||||||
|
// A NestingMap might either be a map or an object, depending on
|
||||||
|
// whether there are dynamically-typed attributes inside, but
|
||||||
|
// that's decided statically and so all values will have the same
|
||||||
|
// kind.
|
||||||
|
if planned.Type().IsObjectType() {
|
||||||
|
plannedAtys := planned.Type().AttributeTypes()
|
||||||
|
configAtys := config.Type().AttributeTypes()
|
||||||
|
for k := range plannedAtys {
|
||||||
|
if _, ok := configAtys[k]; !ok {
|
||||||
|
errs = append(errs, path.NewErrorf("block key %q from plan is not present in config", k))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
path := append(path, cty.GetAttrStep{Name: k})
|
||||||
|
|
||||||
|
plannedEV := planned.GetAttr(k)
|
||||||
|
if !plannedEV.IsKnown() {
|
||||||
|
errs = append(errs, path.NewErrorf("element representing nested block must not be unknown itself; set nested attribute values to unknown instead"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
configEV := config.GetAttr(k)
|
||||||
|
priorEV := cty.NullVal(schema.ImpliedType())
|
||||||
|
if !prior.IsNull() && prior.Type().HasAttribute(k) {
|
||||||
|
priorEV = prior.GetAttr(k)
|
||||||
|
}
|
||||||
|
moreErrs := assertPlannedAttrsValid(schema.Attributes, priorEV, configEV, plannedEV, path)
|
||||||
|
errs = append(errs, moreErrs...)
|
||||||
|
}
|
||||||
|
for k := range configAtys {
|
||||||
|
if _, ok := plannedAtys[k]; !ok {
|
||||||
|
errs = append(errs, path.NewErrorf("block key %q from config is not present in plan", k))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
plannedL := planned.LengthInt()
|
||||||
|
configL := config.LengthInt()
|
||||||
|
if plannedL != configL {
|
||||||
|
errs = append(errs, path.NewErrorf("block count in plan (%d) disagrees with count in config (%d)", plannedL, configL))
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
for it := planned.ElementIterator(); it.Next(); {
|
||||||
|
idx, plannedEV := it.Element()
|
||||||
|
path := append(path, cty.IndexStep{Key: idx})
|
||||||
|
if !plannedEV.IsKnown() {
|
||||||
|
errs = append(errs, path.NewErrorf("element representing nested block must not be unknown itself; set nested attribute values to unknown instead"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
k := idx.AsString()
|
||||||
|
if !config.HasIndex(idx).True() {
|
||||||
|
errs = append(errs, path.NewErrorf("block key %q from plan is not present in config", k))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
configEV := config.Index(idx)
|
||||||
|
priorEV := cty.NullVal(schema.ImpliedType())
|
||||||
|
if !prior.IsNull() && prior.HasIndex(idx).True() {
|
||||||
|
priorEV = prior.Index(idx)
|
||||||
|
}
|
||||||
|
moreErrs := assertPlannedObjectValid(schema, priorEV, configEV, plannedEV, path)
|
||||||
|
errs = append(errs, moreErrs...)
|
||||||
|
}
|
||||||
|
for it := config.ElementIterator(); it.Next(); {
|
||||||
|
idx, _ := it.Element()
|
||||||
|
if !planned.HasIndex(idx).True() {
|
||||||
|
errs = append(errs, path.NewErrorf("block key %q from config is not present in plan", idx.AsString()))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case configschema.NestingSet:
|
||||||
|
// Because set elements have no identifier with which to correlate
|
||||||
|
// them, we can't robustly validate the plan for a nested block
|
||||||
|
// backed by a set, and so unfortunately we need to just trust the
|
||||||
|
// provider to do the right thing. :(
|
||||||
|
//
|
||||||
|
// (In principle we could correlate elements by matching the
|
||||||
|
// subset of attributes explicitly set in config, except for the
|
||||||
|
// special diff suppression rule which allows for there to be a
|
||||||
|
// planned value that is constructed by mixing part of a prior
|
||||||
|
// value with part of a config value, creating an entirely new
|
||||||
|
// element that is not present in either prior nor config.)
|
||||||
|
for it := planned.ElementIterator(); it.Next(); {
|
||||||
|
idx, plannedEV := it.Element()
|
||||||
|
path := append(path, cty.IndexStep{Key: idx})
|
||||||
|
if !plannedEV.IsKnown() {
|
||||||
|
errs = append(errs, path.NewErrorf("element representing nested block must not be unknown itself; set nested attribute values to unknown instead"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
@ -579,6 +579,545 @@ func TestAssertPlanValid(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Attributes with NestedTypes
|
||||||
|
"NestedType attr, no computed, all match": {
|
||||||
|
&configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"a": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingList,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"b": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"b": cty.StringVal("b value"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"b": cty.StringVal("b value"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"b": cty.StringVal("b value"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"NestedType attr, no computed, plan matches, no prior": {
|
||||||
|
&configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"a": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingList,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"b": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.NullVal(cty.Object(map[string]cty.Type{
|
||||||
|
"a": cty.List(cty.Object(map[string]cty.Type{
|
||||||
|
"b": cty.String,
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"b": cty.StringVal("c value"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"b": cty.StringVal("c value"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"NestedType, no computed, invalid change in plan": {
|
||||||
|
&configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"a": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingList,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"b": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.NullVal(cty.Object(map[string]cty.Type{
|
||||||
|
"a": cty.List(cty.Object(map[string]cty.Type{
|
||||||
|
"b": cty.String,
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"b": cty.StringVal("c value"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"b": cty.StringVal("new c value"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
[]string{
|
||||||
|
`.a[0].b: planned value cty.StringVal("new c value") does not match config value cty.StringVal("c value")`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"NestedType attr, no computed, invalid change in plan sensitive": {
|
||||||
|
&configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"a": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingList,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"b": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
Sensitive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.NullVal(cty.Object(map[string]cty.Type{
|
||||||
|
"a": cty.List(cty.Object(map[string]cty.Type{
|
||||||
|
"b": cty.String,
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"b": cty.StringVal("b value"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"b": cty.StringVal("new b value"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
[]string{
|
||||||
|
`.a[0].b: sensitive planned value does not match config value`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"NestedType attr, no computed, diff suppression in plan": {
|
||||||
|
&configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"a": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingList,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"b": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"b": cty.StringVal("b value"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"b": cty.StringVal("new b value"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"b": cty.StringVal("b value"), // plan uses value from prior object
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"NestedType attr, no computed, all null": {
|
||||||
|
&configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"a": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingList,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"b": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.NullVal(cty.DynamicPseudoType),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.NullVal(cty.DynamicPseudoType),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.NullVal(cty.DynamicPseudoType),
|
||||||
|
}),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"NestedType attr, no computed, all zero value": {
|
||||||
|
&configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"a": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingList,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"b": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
|
||||||
|
"b": cty.String,
|
||||||
|
}))),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
|
||||||
|
"b": cty.String,
|
||||||
|
}))),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
|
||||||
|
"b": cty.String,
|
||||||
|
}))),
|
||||||
|
}),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"NestedType NestingSet attribute to null": {
|
||||||
|
&configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bloop": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingSet,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"blop": {
|
||||||
|
Type: cty.String,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.SetVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("ok"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
|
||||||
|
"blop": cty.String,
|
||||||
|
}))),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
|
||||||
|
"blop": cty.String,
|
||||||
|
}))),
|
||||||
|
}),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"NestedType deep nested optional set attribute to null": {
|
||||||
|
&configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bleep": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingList,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bloop": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingSet,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"blome": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bleep": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.SetVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blome": cty.StringVal("ok"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bleep": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.NullVal(cty.Set(
|
||||||
|
cty.Object(map[string]cty.Type{
|
||||||
|
"blome": cty.String,
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bleep": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.NullVal(cty.List(
|
||||||
|
cty.Object(map[string]cty.Type{
|
||||||
|
"blome": cty.String,
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"NestedType deep nested set": {
|
||||||
|
&configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bleep": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingList,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bloop": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingSet,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"blome": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bleep": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.SetVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blome": cty.StringVal("ok"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
// Note: bloop is null in the config
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bleep": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.NullVal(cty.Set(
|
||||||
|
cty.Object(map[string]cty.Type{
|
||||||
|
"blome": cty.String,
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
// provider sends back the prior value, not matching the config
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bleep": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.SetVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blome": cty.StringVal("ok"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
nil, // we cannot validate individual set elements, and trust the provider's response
|
||||||
|
},
|
||||||
|
"NestedType nested computed list attribute": {
|
||||||
|
&configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bloop": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingList,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"blop": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("ok"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
|
||||||
|
"blop": cty.String,
|
||||||
|
}))),
|
||||||
|
}),
|
||||||
|
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("ok"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"NestedType nested list attribute to null": {
|
||||||
|
&configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bloop": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingList,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"blop": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("ok"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
|
||||||
|
"blop": cty.String,
|
||||||
|
}))),
|
||||||
|
}),
|
||||||
|
|
||||||
|
// provider returned the old value
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("ok"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
[]string{".bloop: planned for existence but config wants absense"},
|
||||||
|
},
|
||||||
|
"NestedType nested set attribute to null": {
|
||||||
|
&configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bloop": {
|
||||||
|
NestedType: &configschema.Object{
|
||||||
|
Nesting: configschema.NestingSet,
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"blop": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.SetVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("ok"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
|
||||||
|
"blop": cty.String,
|
||||||
|
}))),
|
||||||
|
}),
|
||||||
|
// provider returned the old value
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bloop": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"blop": cty.StringVal("ok"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
[]string{".bloop: planned for existence but config wants absense"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, test := range tests {
|
for name, test := range tests {
|
||||||
|
@ -649,7 +649,7 @@ func (n *NodeAbstractResourceInstance) plan(
|
|||||||
return plan, state, diags
|
return plan, state, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
proposedNewVal := objchange.ProposedNewObject(schema, unmarkedPriorVal, configValIgnored)
|
proposedNewVal := objchange.ProposedNew(schema, unmarkedPriorVal, configValIgnored)
|
||||||
|
|
||||||
// Call pre-diff hook
|
// Call pre-diff hook
|
||||||
diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) {
|
diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) {
|
||||||
@ -861,7 +861,7 @@ func (n *NodeAbstractResourceInstance) plan(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create a new proposed value from the null state and the config
|
// create a new proposed value from the null state and the config
|
||||||
proposedNewVal = objchange.ProposedNewObject(schema, nullPriorVal, unmarkedConfigVal)
|
proposedNewVal = objchange.ProposedNew(schema, nullPriorVal, unmarkedConfigVal)
|
||||||
|
|
||||||
resp = provider.PlanResourceChange(providers.PlanResourceChangeRequest{
|
resp = provider.PlanResourceChange(providers.PlanResourceChangeRequest{
|
||||||
TypeName: n.Addr.Resource.Resource.Type,
|
TypeName: n.Addr.Resource.Resource.Type,
|
||||||
@ -1423,7 +1423,7 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, currentSt
|
|||||||
// While we don't propose planned changes for data sources, we can
|
// While we don't propose planned changes for data sources, we can
|
||||||
// generate a proposed value for comparison to ensure the data source
|
// generate a proposed value for comparison to ensure the data source
|
||||||
// is returning a result following the rules of the provider contract.
|
// is returning a result following the rules of the provider contract.
|
||||||
proposedVal := objchange.ProposedNewObject(schema, unmarkedPriorVal, unmarkedConfigVal)
|
proposedVal := objchange.ProposedNew(schema, unmarkedPriorVal, unmarkedConfigVal)
|
||||||
if errs := objchange.AssertObjectCompatible(schema, proposedVal, newVal); len(errs) > 0 {
|
if errs := objchange.AssertObjectCompatible(schema, proposedVal, newVal); len(errs) > 0 {
|
||||||
// Resources have the LegacyTypeSystem field to signal when they are
|
// Resources have the LegacyTypeSystem field to signal when they are
|
||||||
// using an SDK which may not produce precise values. While data
|
// using an SDK which may not produce precise values. While data
|
||||||
|
Loading…
Reference in New Issue
Block a user