create index-able types for validation

Since objects and tuples have fixed numbers of elements, we can't return
an unknown version of those during validation. While we could return a
DyanmicVal (which was used previously), that prevents the validation of
outputs and attributes in config references.

Instead, we can return a synthetic type made from a List or Map based
on the configuration, which will allow us to more precisely validate
indexes, attributes, and outputs.
This commit is contained in:
James Bardin 2020-04-23 16:13:45 -04:00
parent 91e243b878
commit 7e6d07ee46
1 changed files with 37 additions and 8 deletions

View File

@ -399,9 +399,15 @@ func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.Sourc
// module instance. // module instance.
moduleInstances := map[addrs.InstanceKey]map[string]cty.Value{} moduleInstances := map[addrs.InstanceKey]map[string]cty.Value{}
// create a dummy object type for validation below
unknownMap := map[string]cty.Type{}
// the structure is based on the configuration, so iterate through all the // the structure is based on the configuration, so iterate through all the
// defined outputs, and add any instance state or changes we find. // defined outputs, and add any instance state or changes we find.
for _, cfg := range outputConfigs { for _, cfg := range outputConfigs {
// record the output names for validation
unknownMap[cfg.Name] = cty.DynamicPseudoType
// get all instance output for this path from the state // get all instance output for this path from the state
for key, states := range stateMap { for key, states := range stateMap {
outputState, ok := states[cfg.Name] outputState, ok := states[cfg.Name]
@ -521,10 +527,22 @@ func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.Sourc
// the objects based on the configuration. // the objects based on the configuration.
if d.Operation == walkValidate { if d.Operation == walkValidate {
// While we know the type here and it would be nice to validate whether // While we know the type here and it would be nice to validate whether
// indexes are valid or not, because tuples have a fixed number of // indexes are valid or not, because tuples and objects have fixed
// elements we can't simply return an unknown tuple type since we have // numbers of elements we can't simply return an unknown value of the
// not expanded any instances during validation. // same type since we have not expanded any instances during
return cty.DynamicVal, diags // validation.
//
// In order to validate the expression a little precisely, we'll create
// an unknown map or list here to get more type information.
ty := cty.Object(unknownMap)
switch {
case callConfig.Count != nil:
ret = cty.UnknownVal(cty.List(ty))
case callConfig.ForEach != nil:
ret = cty.UnknownVal(cty.Map(ty))
default:
ret = cty.UnknownVal(ty)
}
} }
return ret, diags return ret, diags
@ -759,10 +777,21 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
// unknown. // unknown.
if d.Operation == walkValidate { if d.Operation == walkValidate {
// While we know the type here and it would be nice to validate whether // While we know the type here and it would be nice to validate whether
// indexes are valid or not, because tuples have a fixed number of // indexes are valid or not, because tuples and objects have fixed
// elements we can't simply return an unknown tuple type since we have // numbers of elements we can't simply return an unknown value of the
// not expanded any instances during validation. // same type since we have not expanded any instances during
return cty.DynamicVal, diags // validation.
//
// In order to validate the expression a little precisely, we'll create
// an unknown map or list here to get more type information.
switch {
case config.Count != nil:
ret = cty.UnknownVal(cty.List(ty))
case config.ForEach != nil:
ret = cty.UnknownVal(cty.Map(ty))
default:
ret = cty.UnknownVal(ty)
}
} }
return ret, diags return ret, diags