Merge pull request #32962 from hashicorp/jbardin/validate-unknown-coll-attrs

validate unknown nested attribute collections
This commit is contained in:
James Bardin 2023-05-23 11:38:13 -04:00 committed by GitHub
commit 2f308cf948
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 145 additions and 8 deletions

View File

@ -358,10 +358,17 @@ func assertPlannedObjectValid(schema *configschema.Object, prior, config, planne
// 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))
plannedL := planned.Length()
configL := config.Length()
// config wasn't known, then planned should be unknown too
if !plannedL.IsKnown() && !configL.IsKnown() {
return errs
}
lenEqual := plannedL.Equals(configL)
if !lenEqual.IsKnown() || lenEqual.False() {
errs = append(errs, path.NewErrorf("count in plan (%#v) disagrees with count in config (%#v)", plannedL, configL))
return errs
}
for it := planned.ElementIterator(); it.Next(); {
@ -388,6 +395,20 @@ func assertPlannedObjectValid(schema *configschema.Object, prior, config, planne
configVals := map[string]cty.Value{}
priorVals := map[string]cty.Value{}
plannedL := planned.Length()
configL := config.Length()
// config wasn't known, then planned should be unknown too
if !plannedL.IsKnown() && !configL.IsKnown() {
return errs
}
lenEqual := plannedL.Equals(configL)
if !lenEqual.IsKnown() || lenEqual.False() {
errs = append(errs, path.NewErrorf("count in plan (%#v) disagrees with count in config (%#v)", plannedL, configL))
return errs
}
if !planned.IsNull() {
plannedVals = planned.AsValueMap()
}
@ -421,10 +442,17 @@ func assertPlannedObjectValid(schema *configschema.Object, prior, config, planne
}
case configschema.NestingSet:
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))
plannedL := planned.Length()
configL := config.Length()
// config wasn't known, then planned should be unknown too
if !plannedL.IsKnown() && !configL.IsKnown() {
return errs
}
lenEqual := plannedL.Equals(configL)
if !lenEqual.IsKnown() || lenEqual.False() {
errs = append(errs, path.NewErrorf("count in plan (%#v) disagrees with count in config (%#v)", plannedL, configL))
return errs
}
// Because set elements have no identifier with which to correlate

View File

@ -1692,6 +1692,115 @@ func TestAssertPlanValid(t *testing.T) {
}),
nil,
},
// When validating collections we start by comparing length, which
// requires guarding for any unknown values incorrectly returned by the
// provider.
"nested collection attrs planned unknown": {
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"set": {
Computed: true,
Optional: true,
NestedType: &configschema.Object{
Nesting: configschema.NestingSet,
Attributes: map[string]*configschema.Attribute{
"name": {
Type: cty.String,
Computed: true,
Optional: true,
},
},
},
},
"list": {
Computed: true,
Optional: true,
NestedType: &configschema.Object{
Nesting: configschema.NestingList,
Attributes: map[string]*configschema.Attribute{
"name": {
Type: cty.String,
Computed: true,
Optional: true,
},
},
},
},
"map": {
Computed: true,
Optional: true,
NestedType: &configschema.Object{
Nesting: configschema.NestingMap,
Attributes: map[string]*configschema.Attribute{
"name": {
Type: cty.String,
Computed: true,
Optional: true,
},
},
},
},
},
},
cty.ObjectVal(map[string]cty.Value{
"set": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("from_config"),
}),
}),
"list": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("from_config"),
}),
}),
"map": cty.MapVal(map[string]cty.Value{
"key": cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("from_config"),
}),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"set": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("from_config"),
}),
}),
"list": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("from_config"),
}),
}),
"map": cty.MapVal(map[string]cty.Value{
"key": cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("from_config"),
}),
}),
}),
// provider cannot override the config
cty.ObjectVal(map[string]cty.Value{
"set": cty.UnknownVal(cty.Set(
cty.Object(map[string]cty.Type{
"name": cty.String,
}),
)),
"list": cty.UnknownVal(cty.Set(
cty.Object(map[string]cty.Type{
"name": cty.String,
}),
)),
"map": cty.UnknownVal(cty.Map(
cty.Object(map[string]cty.Type{
"name": cty.String,
}),
)),
}),
[]string{
`.set: count in plan (cty.UnknownVal(cty.Number)) disagrees with count in config (cty.NumberIntVal(1))`,
`.list: count in plan (cty.UnknownVal(cty.Number)) disagrees with count in config (cty.NumberIntVal(1))`,
`.map: count in plan (cty.UnknownVal(cty.Number)) disagrees with count in config (cty.NumberIntVal(1))`,
},
},
}
for name, test := range tests {