Merge pull request #29482 from hashicorp/jbardin/computed-obj-attrs
Computed values within nested object types in `AssertPlanValid`
This commit is contained in:
commit
7c02ef6220
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
// 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()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
path := append(path, cty.GetAttrStep{Name: k})
|
||||
|
||||
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)
|
||||
priorEV, ok := priorVals[k]
|
||||
if !ok {
|
||||
priorEV = cty.NullVal(schema.ImpliedType())
|
||||
}
|
||||
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))
|
||||
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
|
||||
}
|
||||
}
|
||||
} 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 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...)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -1222,6 +1222,236 @@ func TestAssertPlanValid(t *testing.T) {
|
|||
}),
|
||||
[]string{`.bloop: planned value cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"blop":cty.StringVal("ok")})}) for a non-computed attribute`},
|
||||
},
|
||||
"computed within nested objects": {
|
||||
&configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"map": {
|
||||
NestedType: &configschema.Object{
|
||||
Nesting: configschema.NestingMap,
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"name": {
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// When an object has dynamic attrs, the map may be
|
||||
// handled as an object.
|
||||
"map_as_obj": {
|
||||
NestedType: &configschema.Object{
|
||||
Nesting: configschema.NestingMap,
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"name": {
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"list": {
|
||||
NestedType: &configschema.Object{
|
||||
Nesting: configschema.NestingList,
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"name": {
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"set": {
|
||||
NestedType: &configschema.Object{
|
||||
Nesting: configschema.NestingSet,
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"name": {
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"single": {
|
||||
NestedType: &configschema.Object{
|
||||
Nesting: configschema.NestingSingle,
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"name": {
|
||||
Type: cty.DynamicPseudoType,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.NullVal(cty.Object(map[string]cty.Type{
|
||||
"map": cty.Map(cty.Object(map[string]cty.Type{
|
||||
"name": cty.String,
|
||||
})),
|
||||
"map_as_obj": cty.Map(cty.Object(map[string]cty.Type{
|
||||
"name": cty.DynamicPseudoType,
|
||||
})),
|
||||
"list": cty.List(cty.Object(map[string]cty.Type{
|
||||
"name": cty.String,
|
||||
})),
|
||||
"set": cty.Set(cty.Object(map[string]cty.Type{
|
||||
"name": cty.String,
|
||||
})),
|
||||
"single": cty.Object(map[string]cty.Type{
|
||||
"name": cty.String,
|
||||
}),
|
||||
})),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"map": cty.MapVal(map[string]cty.Value{
|
||||
"one": cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.NullVal(cty.String),
|
||||
}),
|
||||
}),
|
||||
"map_as_obj": cty.MapVal(map[string]cty.Value{
|
||||
"one": cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.NullVal(cty.DynamicPseudoType),
|
||||
}),
|
||||
}),
|
||||
"list": cty.ListVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.NullVal(cty.String),
|
||||
}),
|
||||
}),
|
||||
"set": cty.SetVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.NullVal(cty.String),
|
||||
}),
|
||||
}),
|
||||
"single": cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.NullVal(cty.String),
|
||||
}),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"map": cty.MapVal(map[string]cty.Value{
|
||||
"one": cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.NullVal(cty.String),
|
||||
}),
|
||||
}),
|
||||
"map_as_obj": cty.ObjectVal(map[string]cty.Value{
|
||||
"one": cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.StringVal("computed"),
|
||||
}),
|
||||
}),
|
||||
"list": cty.ListVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.NullVal(cty.String),
|
||||
}),
|
||||
}),
|
||||
"set": cty.SetVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.NullVal(cty.String),
|
||||
}),
|
||||
}),
|
||||
"single": cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.NullVal(cty.String),
|
||||
}),
|
||||
}),
|
||||
nil,
|
||||
},
|
||||
"computed nested objects": {
|
||||
&configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"map": {
|
||||
NestedType: &configschema.Object{
|
||||
Nesting: configschema.NestingMap,
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"name": {
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
},
|
||||
Computed: true,
|
||||
},
|
||||
"list": {
|
||||
NestedType: &configschema.Object{
|
||||
Nesting: configschema.NestingList,
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"name": {
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
},
|
||||
Computed: true,
|
||||
},
|
||||
"set": {
|
||||
NestedType: &configschema.Object{
|
||||
Nesting: configschema.NestingSet,
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"name": {
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
},
|
||||
Computed: true,
|
||||
},
|
||||
"single": {
|
||||
NestedType: &configschema.Object{
|
||||
Nesting: configschema.NestingSingle,
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"name": {
|
||||
Type: cty.DynamicPseudoType,
|
||||
},
|
||||
},
|
||||
},
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.NullVal(cty.Object(map[string]cty.Type{
|
||||
"map": cty.Map(cty.Object(map[string]cty.Type{
|
||||
"name": cty.String,
|
||||
})),
|
||||
"list": cty.List(cty.Object(map[string]cty.Type{
|
||||
"name": cty.String,
|
||||
})),
|
||||
"set": cty.Set(cty.Object(map[string]cty.Type{
|
||||
"name": cty.String,
|
||||
})),
|
||||
"single": cty.Object(map[string]cty.Type{
|
||||
"name": cty.String,
|
||||
}),
|
||||
})),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
|
||||
"name": cty.String,
|
||||
}))),
|
||||
"list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
|
||||
"name": cty.String,
|
||||
}))),
|
||||
"set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
|
||||
"name": cty.String,
|
||||
}))),
|
||||
"single": cty.NullVal(cty.Object(map[string]cty.Type{
|
||||
"name": cty.String,
|
||||
})),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"map": cty.MapVal(map[string]cty.Value{
|
||||
"one": cty.UnknownVal(cty.Object(map[string]cty.Type{
|
||||
"name": cty.String,
|
||||
})),
|
||||
}),
|
||||
"list": cty.ListVal([]cty.Value{
|
||||
cty.UnknownVal(cty.Object(map[string]cty.Type{
|
||||
"name": cty.String,
|
||||
})),
|
||||
}),
|
||||
"set": cty.SetVal([]cty.Value{
|
||||
cty.UnknownVal(cty.Object(map[string]cty.Type{
|
||||
"name": cty.String,
|
||||
})),
|
||||
}),
|
||||
"single": cty.UnknownVal(cty.Object(map[string]cty.Type{
|
||||
"name": cty.String,
|
||||
})),
|
||||
}),
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
|
|
Loading…
Reference in New Issue