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.
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
// defined outputs, and add any instance state or changes we find.
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
for key, states := range stateMap {
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.
if d.Operation == walkValidate {
// 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
// elements we can't simply return an unknown tuple type since we have
// not expanded any instances during validation.
return cty.DynamicVal, diags
// indexes are valid or not, because tuples and objects have fixed
// numbers of elements we can't simply return an unknown value of the
// same type since we have not expanded any instances during
// 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
@ -759,10 +777,21 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
// unknown.
if d.Operation == walkValidate {
// 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
// elements we can't simply return an unknown tuple type since we have
// not expanded any instances during validation.
return cty.DynamicVal, diags
// indexes are valid or not, because tuples and objects have fixed
// numbers of elements we can't simply return an unknown value of the
// same type since we have not expanded any instances during
// 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