nested object values can be computed
While blocks were not allowed to be computed by the provider, nested objects can be. Remove the errors regarding blocks and verify unknown values are valid.
This commit is contained in:
parent
903797084a
commit
ea68d79ea2
|
@ -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 {
|
func assertPlanValid(schema *configschema.Block, priorState, config, plannedState cty.Value, path cty.Path) []error {
|
||||||
var errs []error
|
var errs []error
|
||||||
if plannedState.IsNull() && !config.IsNull() {
|
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
|
return errs
|
||||||
}
|
}
|
||||||
if config.IsNull() && !plannedState.IsNull() {
|
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
|
return errs
|
||||||
}
|
}
|
||||||
if plannedState.IsNull() {
|
if plannedState.IsNull() {
|
||||||
|
@ -286,6 +286,11 @@ func assertPlannedValueValid(attrS *configschema.Attribute, priorV, configV, pla
|
||||||
}
|
}
|
||||||
return errs
|
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
|
// 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
|
var errs []error
|
||||||
|
|
||||||
if planned.IsNull() && !config.IsNull() {
|
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
|
return errs
|
||||||
}
|
}
|
||||||
if config.IsNull() && !planned.IsNull() {
|
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
|
return errs
|
||||||
}
|
}
|
||||||
if planned.IsNull() {
|
if planned.IsNull() {
|
||||||
|
@ -349,10 +354,6 @@ func assertPlannedObjectValid(schema *configschema.Object, prior, config, planne
|
||||||
for it := planned.ElementIterator(); it.Next(); {
|
for it := planned.ElementIterator(); it.Next(); {
|
||||||
idx, plannedEV := it.Element()
|
idx, plannedEV := it.Element()
|
||||||
path := append(path, cty.IndexStep{Key: idx})
|
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() {
|
if !config.HasIndex(idx).True() {
|
||||||
continue // should never happen since we checked the lengths above
|
continue // should never happen since we checked the lengths above
|
||||||
}
|
}
|
||||||
|
@ -387,16 +388,11 @@ func assertPlannedObjectValid(schema *configschema.Object, prior, config, planne
|
||||||
for k, plannedEV := range plannedVals {
|
for k, plannedEV := range plannedVals {
|
||||||
configEV, ok := configVals[k]
|
configEV, ok := configVals[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
errs = append(errs, path.NewErrorf("block key %q from plan is not present in config", k))
|
errs = append(errs, path.NewErrorf("map key %q from plan is not present in config", k))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
path := append(path, cty.GetAttrStep{Name: k})
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
priorEV, ok := priorVals[k]
|
priorEV, ok := priorVals[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
priorEV = cty.NullVal(schema.ImpliedType())
|
priorEV = cty.NullVal(schema.ImpliedType())
|
||||||
|
@ -406,31 +402,22 @@ func assertPlannedObjectValid(schema *configschema.Object, prior, config, planne
|
||||||
}
|
}
|
||||||
for k := range configVals {
|
for k := range configVals {
|
||||||
if _, ok := plannedVals[k]; !ok {
|
if _, ok := plannedVals[k]; !ok {
|
||||||
errs = append(errs, path.NewErrorf("block key %q from config is not present in plan", k))
|
errs = append(errs, path.NewErrorf("map key %q from config is not present in plan", k))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case configschema.NestingSet:
|
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
|
// 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
|
// backed by a set, and so unfortunately we need to just trust the
|
||||||
// provider to do the right thing. :(
|
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
|
|
|
@ -1222,7 +1222,7 @@ 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`},
|
[]string{`.bloop: planned value cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"blop":cty.StringVal("ok")})}) for a non-computed attribute`},
|
||||||
},
|
},
|
||||||
"computed in nested objects": {
|
"computed within nested objects": {
|
||||||
&configschema.Block{
|
&configschema.Block{
|
||||||
Attributes: map[string]*configschema.Attribute{
|
Attributes: map[string]*configschema.Attribute{
|
||||||
"map": {
|
"map": {
|
||||||
|
@ -1353,6 +1353,105 @@ func TestAssertPlanValid(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
nil,
|
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 {
|
for name, test := range tests {
|
||||||
|
|
Loading…
Reference in New Issue