diff --git a/internal/plans/objchange/plan_valid.go b/internal/plans/objchange/plan_valid.go index e26979e84..5bcfe8dd5 100644 --- a/internal/plans/objchange/plan_valid.go +++ b/internal/plans/objchange/plan_valid.go @@ -368,71 +368,46 @@ func assertPlannedObjectValid(schema *configschema.Object, prior, config, planne 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}) + // whether there are dynamically-typed attributes inside, so we will + // break these down to maps to handle them both in the same manner. + plannedVals := map[string]cty.Value{} + configVals := map[string]cty.Value{} + priorVals := map[string]cty.Value{} - 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...) + if !planned.IsNull() { + plannedVals = planned.AsValueMap() + } + if !config.IsNull() { + configVals = config.AsValueMap() + } + if !prior.IsNull() { + priorVals = prior.AsValueMap() + } + + for k, plannedEV := range plannedVals { + configEV, ok := configVals[k] + if !ok { + errs = append(errs, path.NewErrorf("block key %q from plan is not present in config", k)) + continue } - 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 - } + path := append(path, cty.GetAttrStep{Name: 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 } - } 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 + + priorEV, ok := priorVals[k] + if !ok { + priorEV = cty.NullVal(schema.ImpliedType()) } - 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 := assertPlannedAttrsValid(schema.Attributes, 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 - } + moreErrs := assertPlannedAttrsValid(schema.Attributes, priorEV, configEV, plannedEV, path) + errs = append(errs, moreErrs...) + } + for k := range configVals { + if _, ok := plannedVals[k]; !ok { + errs = append(errs, path.NewErrorf("block key %q from config is not present in plan", k)) + continue } } diff --git a/internal/plans/objchange/plan_valid_test.go b/internal/plans/objchange/plan_valid_test.go index 783e7e15f..28150002b 100644 --- a/internal/plans/objchange/plan_valid_test.go +++ b/internal/plans/objchange/plan_valid_test.go @@ -1307,7 +1307,7 @@ func TestAssertPlanValid(t *testing.T) { "name": cty.NullVal(cty.String), }), }), - "map_as_obj": cty.ObjectVal(map[string]cty.Value{ + "map_as_obj": cty.MapVal(map[string]cty.Value{ "one": cty.ObjectVal(map[string]cty.Value{ "name": cty.NullVal(cty.DynamicPseudoType), }),