mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-15 19:22:46 -06:00
fix panics when handling null values in maps
NestingMap structures are not well tested, and we panic in many situations when null crops up. Fix the first test cases and start refactoring best we can. This probably won't go so far as making all the objchange functions generic over Block and Object, but we can simplify a lot and verify parity in implementations for now.
This commit is contained in:
parent
8e917e5513
commit
fcbfc365e6
@ -275,6 +275,14 @@ func proposedNewNestedBlock(schema *configschema.NestedBlock, prior, config cty.
|
||||
return newV
|
||||
}
|
||||
|
||||
func proposedNewObjectAttributes(attrs map[string]*configschema.Attribute, prior, config cty.Value) cty.Value {
|
||||
if config.IsNull() {
|
||||
return config
|
||||
}
|
||||
|
||||
return cty.ObjectVal(proposedNewAttributes(attrs, prior, config))
|
||||
}
|
||||
|
||||
func proposedNewAttributes(attrs map[string]*configschema.Attribute, prior, config cty.Value) map[string]cty.Value {
|
||||
newAttrs := make(map[string]cty.Value, len(attrs))
|
||||
for name, attr := range attrs {
|
||||
@ -324,7 +332,7 @@ func proposedNewAttributes(attrs map[string]*configschema.Attribute, prior, conf
|
||||
|
||||
func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value) cty.Value {
|
||||
// if the config isn't known at all, then we must use that value
|
||||
if !config.IsNull() && !config.IsKnown() {
|
||||
if !config.IsKnown() {
|
||||
return config
|
||||
}
|
||||
|
||||
@ -340,7 +348,7 @@ func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value)
|
||||
break
|
||||
}
|
||||
|
||||
newV = cty.ObjectVal(proposedNewAttributes(schema.Attributes, prior, config))
|
||||
newV = proposedNewObjectAttributes(schema.Attributes, prior, config)
|
||||
|
||||
case configschema.NestingList:
|
||||
// Nested blocks are correlated by index.
|
||||
@ -361,8 +369,8 @@ func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value)
|
||||
}
|
||||
priorEV := prior.Index(idx)
|
||||
|
||||
newEV := proposedNewAttributes(schema.Attributes, priorEV, configEV)
|
||||
newVals = append(newVals, cty.ObjectVal(newEV))
|
||||
newEV := proposedNewObjectAttributes(schema.Attributes, priorEV, configEV)
|
||||
newVals = append(newVals, newEV)
|
||||
}
|
||||
// Despite the name, a NestingList might also be a tuple, if
|
||||
// its nested schema contains dynamically-typed attributes.
|
||||
@ -374,58 +382,55 @@ func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value)
|
||||
}
|
||||
|
||||
case configschema.NestingMap:
|
||||
configVLen := 0
|
||||
if config.IsKnown() && !config.IsNull() {
|
||||
configVLen = config.LengthInt()
|
||||
}
|
||||
|
||||
if configVLen == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
newVals := make(map[string]cty.Value, configVLen)
|
||||
|
||||
// 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 := proposedNewAttributes(schema.Attributes, priorEV, configEV)
|
||||
newVals[name] = cty.ObjectVal(newEV)
|
||||
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
|
||||
}
|
||||
// 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)
|
||||
priorEV := prior.GetAttr(name)
|
||||
newEV := proposedNewObjectAttributes(schema.Attributes, 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 {
|
||||
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 := proposedNewAttributes(schema.Attributes, priorEV, configEV)
|
||||
newVals[k] = cty.ObjectVal(newEV)
|
||||
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
|
||||
}
|
||||
newV = cty.MapVal(newVals)
|
||||
priorEV := prior.Index(idx)
|
||||
|
||||
newEV := proposedNewObjectAttributes(schema.Attributes, priorEV, configEV)
|
||||
newVals[k] = newEV
|
||||
}
|
||||
newV = cty.MapVal(newVals)
|
||||
}
|
||||
|
||||
case configschema.NestingSet:
|
||||
@ -461,8 +466,8 @@ func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value)
|
||||
if priorEV == cty.NilVal {
|
||||
newVals = append(newVals, configEV)
|
||||
} else {
|
||||
newEV := proposedNewAttributes(schema.Attributes, priorEV, configEV)
|
||||
newVals = append(newVals, cty.ObjectVal(newEV))
|
||||
newEV := proposedNewObjectAttributes(schema.Attributes, priorEV, configEV)
|
||||
newVals = append(newVals, newEV)
|
||||
}
|
||||
}
|
||||
newV = cty.SetVal(newVals)
|
||||
|
@ -752,6 +752,120 @@ func TestProposedNew(t *testing.T) {
|
||||
}),
|
||||
}),
|
||||
},
|
||||
|
||||
"prior optional computed nested map elem to null": {
|
||||
&configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"bloop": {
|
||||
NestedType: &configschema.Object{
|
||||
Nesting: configschema.NestingMap,
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"blop": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
"bleep": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"bloop": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"blop": cty.StringVal("glub"),
|
||||
"bleep": cty.StringVal("computed"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"blop": cty.StringVal("blub"),
|
||||
"bleep": cty.StringVal("computed"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"bloop": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.NullVal(cty.Object(map[string]cty.Type{
|
||||
"blop": cty.String,
|
||||
"bleep": cty.String,
|
||||
})),
|
||||
"c": cty.ObjectVal(map[string]cty.Value{
|
||||
"blop": cty.StringVal("blub"),
|
||||
"bleep": cty.NullVal(cty.String),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"bloop": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.NullVal(cty.Object(map[string]cty.Type{
|
||||
"blop": cty.String,
|
||||
"bleep": cty.String,
|
||||
})),
|
||||
"c": cty.ObjectVal(map[string]cty.Value{
|
||||
"blop": cty.StringVal("blub"),
|
||||
"bleep": cty.NullVal(cty.String),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
|
||||
"prior optional computed nested map to null": {
|
||||
&configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"bloop": {
|
||||
NestedType: &configschema.Object{
|
||||
Nesting: configschema.NestingMap,
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"blop": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
"bleep": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"bloop": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"blop": cty.StringVal("glub"),
|
||||
"bleep": cty.StringVal("computed"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"blop": cty.StringVal("blub"),
|
||||
"bleep": cty.StringVal("computed"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"bloop": cty.NullVal(cty.Map(
|
||||
cty.Object(map[string]cty.Type{
|
||||
"blop": cty.String,
|
||||
"bleep": cty.String,
|
||||
}),
|
||||
)),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"bloop": cty.NullVal(cty.Map(
|
||||
cty.Object(map[string]cty.Type{
|
||||
"blop": cty.String,
|
||||
"bleep": cty.String,
|
||||
}),
|
||||
)),
|
||||
}),
|
||||
},
|
||||
|
||||
"prior nested map with dynamic": {
|
||||
&configschema.Block{
|
||||
BlockTypes: map[string]*configschema.NestedBlock{
|
||||
|
Loading…
Reference in New Issue
Block a user