From 7e6d07ee46ae7bad6e30f8913fe5b90168ad5a66 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 23 Apr 2020 16:13:45 -0400 Subject: [PATCH] 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. --- terraform/evaluate.go | 45 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/terraform/evaluate.go b/terraform/evaluate.go index b18167dc9..79397f1b7 100644 --- a/terraform/evaluate.go +++ b/terraform/evaluate.go @@ -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