Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Computed values within nested object types in AssertPlanValid #29482

Merged
merged 4 commits into from
Aug 31, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 49 additions & 87 deletions internal/plans/objchange/plan_valid.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ func AssertPlanValid(schema *configschema.Block, priorState, config, plannedStat
func assertPlanValid(schema *configschema.Block, priorState, config, plannedState cty.Value, path cty.Path) []error {
var errs []error
if plannedState.IsNull() && !config.IsNull() {
errs = append(errs, path.NewErrorf("planned for absense but config wants existence"))
errs = append(errs, path.NewErrorf("planned for absence but config wants existence"))
return errs
}
if config.IsNull() && !plannedState.IsNull() {
errs = append(errs, path.NewErrorf("planned for existence but config wants absense"))
errs = append(errs, path.NewErrorf("planned for existence but config wants absence"))
return errs
}
if plannedState.IsNull() {
Expand Down Expand Up @@ -286,6 +286,11 @@ func assertPlannedValueValid(attrS *configschema.Attribute, priorV, configV, pla
}
return errs
}
} else {
if attrS.Computed {
errs = append(errs, path.NewErrorf("configuration present for computed attribute"))
return errs
}
}

// If this attribute has a NestedType, validate the nested object
Expand Down Expand Up @@ -317,11 +322,11 @@ func assertPlannedObjectValid(schema *configschema.Object, prior, config, planne
var errs []error

if planned.IsNull() && !config.IsNull() {
errs = append(errs, path.NewErrorf("planned for absense but config wants existence"))
errs = append(errs, path.NewErrorf("planned for absence but config wants existence"))
return errs
}
if config.IsNull() && !planned.IsNull() {
errs = append(errs, path.NewErrorf("planned for existence but config wants absense"))
errs = append(errs, path.NewErrorf("planned for existence but config wants absence"))
return errs
}
if planned.IsNull() {
Expand Down Expand Up @@ -349,10 +354,6 @@ func assertPlannedObjectValid(schema *configschema.Object, prior, config, planne
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
}
Expand All @@ -368,94 +369,55 @@ 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{}

if !planned.IsNull() {
plannedVals = planned.AsValueMap()
}
if !config.IsNull() {
configVals = config.AsValueMap()
}
if !prior.IsNull() {
priorVals = prior.AsValueMap()
}

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 k, plannedEV := range plannedVals {
configEV, ok := configVals[k]
if !ok {
errs = append(errs, path.NewErrorf("map key %q from plan is not present in config", k))
continue
}
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...)
path := append(path, cty.GetAttrStep{Name: k})

priorEV, ok := priorVals[k]
if !ok {
priorEV = cty.NullVal(schema.ImpliedType())
}
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("map key %q from config is not present in plan", k))
continue
}
}

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))
return errs
}
// Because set elements have no identifier with which to correlate
// them, we can't robustly validate the plan for a nested block
// them, we can't robustly validate the plan for a nested object
// 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
}
}
// provider to do the right thing.
}

return errs
Expand Down
Loading