From 3cbedd38171789866bb385183dcdf8a6745e5c9f Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 8 Jan 2025 10:56:05 -0800 Subject: [PATCH] plans/objchange: Split assertNestedBlockCompatibleMap into two parts Unlike the other nesting modes, we need to use some quite different code for comparing object-backed vs. map-backed blocks, which are both possible interpretations of the NestingMap mode depending on whether the inner block type has any dynamically-typed attributes. Therefore we split that case into two parts depending on what type kind we find, so that each of the parts can then be shaped more like the other type-specific variants of assertNestedBlockCompatible. (This also removes one level of if nesting to placate the nestif linter.) Signed-off-by: Martin Atkins --- internal/plans/objchange/compatible.go | 105 +++++++++++++------------ 1 file changed, 55 insertions(+), 50 deletions(-) diff --git a/internal/plans/objchange/compatible.go b/internal/plans/objchange/compatible.go index b091b2ba4f..c2256bf1e6 100644 --- a/internal/plans/objchange/compatible.go +++ b/internal/plans/objchange/compatible.go @@ -101,7 +101,16 @@ func assertNestedBlockCompatible(plannedV, actualV cty.Value, blockS *configsche case configschema.NestingList: return assertNestedBlockCompatibleList(plannedV, actualV, blockS, path) case configschema.NestingMap: - return assertNestedBlockCompatibleMap(plannedV, actualV, blockS, path) + // 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 both values will have the same + // kind. Our handling of each is slightly different in the details, + // but both have similar goals. + if plannedV.Type().IsObjectType() { + return assertNestedBlockCompatibleMapAsObject(plannedV, actualV, blockS, path) + } else { + return assertNestedBlockCompatibleMapAsMap(plannedV, actualV, blockS, path) + } case configschema.NestingSet: return assertNestedBlockCompatibleSet(plannedV, actualV, blockS, path) default: @@ -151,57 +160,53 @@ func assertNestedBlockCompatibleList(plannedV, actualV cty.Value, blockS *config return errs } -func assertNestedBlockCompatibleMap(plannedV, actualV cty.Value, blockS *configschema.NestedBlock, path cty.Path) []error { - // 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 both values will have the same - // kind. - - var errs []error - - if plannedV.Type().IsObjectType() { - plannedAtys := plannedV.Type().AttributeTypes() - actualAtys := actualV.Type().AttributeTypes() - for k := range plannedAtys { - if _, ok := actualAtys[k]; !ok { - errs = append(errs, path.NewErrorf("block key %q has vanished", k)) - continue - } - - plannedEV := plannedV.GetAttr(k) - actualEV := actualV.GetAttr(k) - moreErrs := assertObjectCompatible(&blockS.Block, plannedEV, actualEV, append(path, cty.GetAttrStep{Name: k})) - errs = append(errs, moreErrs...) - } - if plannedV.IsKnown() { // new blocks may appear if unknown blocks were present in the plan - for k := range actualAtys { - if _, ok := plannedAtys[k]; !ok { - errs = append(errs, path.NewErrorf("new block key %q has appeared", k)) - continue - } - } - } - } else { - if !plannedV.IsKnown() || plannedV.IsNull() || actualV.IsNull() { - return errs - } - plannedL := plannedV.LengthInt() - actualL := actualV.LengthInt() - if plannedL != actualL && plannedV.IsKnown() { // new blocks may appear if unknown blocks were present in the plan - errs = append(errs, path.NewErrorf("block count changed from %d to %d", plannedL, actualL)) - return errs - } - for it := plannedV.ElementIterator(); it.Next(); { - idx, plannedEV := it.Element() - if !actualV.HasIndex(idx).True() { - continue - } - actualEV := actualV.Index(idx) - moreErrs := assertObjectCompatible(&blockS.Block, plannedEV, actualEV, append(path, cty.IndexStep{Key: idx})) - errs = append(errs, moreErrs...) - } +func assertNestedBlockCompatibleMapAsMap(plannedV, actualV cty.Value, blockS *configschema.NestedBlock, path cty.Path) []error { + if !plannedV.IsKnown() || plannedV.IsNull() || actualV.IsNull() { + return nil } + var errs []error + plannedL := plannedV.LengthInt() + actualL := actualV.LengthInt() + if plannedL != actualL && plannedV.IsKnown() { // new blocks may appear if unknown blocks were present in the plan + errs = append(errs, path.NewErrorf("block count changed from %d to %d", plannedL, actualL)) + return errs + } + for it := plannedV.ElementIterator(); it.Next(); { + idx, plannedEV := it.Element() + if !actualV.HasIndex(idx).True() { + continue + } + actualEV := actualV.Index(idx) + moreErrs := assertObjectCompatible(&blockS.Block, plannedEV, actualEV, append(path, cty.IndexStep{Key: idx})) + errs = append(errs, moreErrs...) + } + return errs +} + +func assertNestedBlockCompatibleMapAsObject(plannedV, actualV cty.Value, blockS *configschema.NestedBlock, path cty.Path) []error { + var errs []error + plannedAtys := plannedV.Type().AttributeTypes() + actualAtys := actualV.Type().AttributeTypes() + for k := range plannedAtys { + if _, ok := actualAtys[k]; !ok { + errs = append(errs, path.NewErrorf("block key %q has vanished", k)) + continue + } + + plannedEV := plannedV.GetAttr(k) + actualEV := actualV.GetAttr(k) + moreErrs := assertObjectCompatible(&blockS.Block, plannedEV, actualEV, append(path, cty.GetAttrStep{Name: k})) + errs = append(errs, moreErrs...) + } + if plannedV.IsKnown() { // new blocks may appear if unknown blocks were present in the plan + for k := range actualAtys { + if _, ok := plannedAtys[k]; !ok { + errs = append(errs, path.NewErrorf("new block key %q has appeared", k)) + continue + } + } + } return errs }