From d9603d5bc5a223c9d1cd604b01d402ddcbd0ac1f Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 6 Dec 2018 11:42:33 -0800 Subject: [PATCH] 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. --- configs/configupgrade/analysis.go | 41 ++++++++++++++++++++++++++ configs/configupgrade/analysis_expr.go | 17 +++++++++-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/configs/configupgrade/analysis.go b/configs/configupgrade/analysis.go index 9eb93f5ac9..5f513a3337 100644 --- a/configs/configupgrade/analysis.go +++ b/configs/configupgrade/analysis.go @@ -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()) diff --git a/configs/configupgrade/analysis_expr.go b/configs/configupgrade/analysis_expr.go index d6dc02bcd4..44a65af158 100644 --- a/configs/configupgrade/analysis_expr.go +++ b/configs/configupgrade/analysis_expr.go @@ -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 + } }