configs/configupgrade: Do type inference with input variables

By collecting information about the input variables during analysis, we
can return approximate type information for any references to those
variables in expressions.

Since Terraform 0.11 allowed maps of maps and lists of lists in certain
circumstances even though this was documented as forbidden, we
conservatively return collection types whose element types are unknown
here, which allows us to do shallow inference on them but will cause
us to get an incomplete result if any operations are performed on
elements of the list or map value.
This commit is contained in:
Martin Atkins 2018-12-06 11:42:33 -08:00
parent a2d9634dbf
commit d9603d5bc5
2 changed files with 55 additions and 3 deletions

View File

@ -3,6 +3,7 @@ package configupgrade
import (
"fmt"
"log"
"strings"
hcl1 "github.com/hashicorp/hcl"
hcl1ast "github.com/hashicorp/hcl/hcl/ast"
@ -23,6 +24,7 @@ type analysis struct {
ProvisionerSchemas map[string]*configschema.Block
ResourceProviderType map[addrs.Resource]string
ResourceHasCount map[addrs.Resource]bool
VariableTypes map[string]string
}
// analyze processes the configuration files included inside the receiver
@ -34,6 +36,7 @@ func (u *Upgrader) analyze(ms ModuleSources) (*analysis, error) {
ProvisionerSchemas: make(map[string]*configschema.Block),
ResourceProviderType: make(map[addrs.Resource]string),
ResourceHasCount: make(map[addrs.Resource]bool),
VariableTypes: make(map[string]string),
}
m := &moduledeps.Module{
@ -188,6 +191,44 @@ func (u *Upgrader) analyze(ms ModuleSources) (*analysis, error) {
ret.ResourceProviderType[rAddr] = inst.Type()
}
}
if variablesList := list.Filter("variable"); len(variablesList.Items) > 0 {
variableObjs := variablesList.Children()
for _, variableObj := range variableObjs.Items {
if len(variableObj.Keys) != 1 {
return nil, fmt.Errorf("variable block has wrong number of labels")
}
name := variableObj.Keys[0].Token.Value().(string)
var listVal *hcl1ast.ObjectList
if ot, ok := variableObj.Val.(*hcl1ast.ObjectType); ok {
listVal = ot.List
} else {
return nil, fmt.Errorf("variable %q: must be a block", name)
}
var typeStr string
if a := listVal.Filter("type"); len(a.Items) > 0 {
err := hcl1.DecodeObject(&typeStr, a.Items[0].Val)
if err != nil {
return nil, fmt.Errorf("Error reading type for variable %q: %s", name, err)
}
} else if a := listVal.Filter("default"); len(a.Items) > 0 {
switch a.Items[0].Val.(type) {
case *hcl1ast.ObjectType:
typeStr = "map"
case *hcl1ast.ListType:
typeStr = "list"
default:
typeStr = "string"
}
} else {
typeStr = "string"
}
ret.VariableTypes[name] = strings.TrimSpace(typeStr)
}
}
}
providerFactories, err := u.Providers.ResolveProviders(m.PluginRequirements())

View File

@ -68,7 +68,6 @@ func (d analysisData) StaticValidateReferences(refs []*addrs.Reference, self add
func (d analysisData) GetCountAttr(addr addrs.CountAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
// All valid count attributes are numbers
log.Printf("[TRACE] configupgrade: Determining type for %s", addr)
return cty.UnknownVal(cty.Number), nil
}
@ -149,8 +148,20 @@ func (d analysisData) GetTerraformAttr(addrs.TerraformAttr, tfdiags.SourceRange)
return cty.UnknownVal(cty.String), nil
}
func (d analysisData) GetInputVariable(addrs.InputVariable, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
func (d analysisData) GetInputVariable(addr addrs.InputVariable, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
// TODO: Collect shallow type information (list vs. map vs. string vs. unknown)
// in analysis and then return a similarly-approximate type here.
return cty.DynamicVal, nil
log.Printf("[TRACE] configupgrade: Determining type for %s", addr)
name := addr.Name
typeName := d.an.VariableTypes[name]
switch typeName {
case "list":
return cty.UnknownVal(cty.List(cty.DynamicPseudoType)), nil
case "map":
return cty.UnknownVal(cty.Map(cty.DynamicPseudoType)), nil
case "string":
return cty.UnknownVal(cty.String), nil
default:
return cty.DynamicVal, nil
}
}