github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/configs/configupgrade/analysis_expr.go (about) 1 package configupgrade 2 3 import ( 4 "log" 5 6 hcl2 "github.com/hashicorp/hcl/v2" 7 hcl2syntax "github.com/hashicorp/hcl/v2/hclsyntax" 8 "github.com/zclconf/go-cty/cty" 9 10 "github.com/hashicorp/terraform/addrs" 11 "github.com/hashicorp/terraform/lang" 12 "github.com/hashicorp/terraform/tfdiags" 13 ) 14 15 // InferExpressionType attempts to determine a result type for the given 16 // expression source code, which should already have been upgraded to new 17 // expression syntax. 18 // 19 // If self is non-nil, it will determine the meaning of the special "self" 20 // reference. 21 // 22 // If such an inference isn't possible, either because of limitations of 23 // static analysis or because of errors in the expression, the result is 24 // cty.DynamicPseudoType indicating "unknown". 25 func (an *analysis) InferExpressionType(src []byte, self addrs.Referenceable) cty.Type { 26 expr, diags := hcl2syntax.ParseExpression(src, "", hcl2.Pos{Line: 1, Column: 1}) 27 if diags.HasErrors() { 28 // If there's a syntax error then analysis is impossible. 29 return cty.DynamicPseudoType 30 } 31 32 data := analysisData{an} 33 scope := &lang.Scope{ 34 Data: data, 35 SelfAddr: self, 36 PureOnly: false, 37 BaseDir: ".", 38 } 39 val, _ := scope.EvalExpr(expr, cty.DynamicPseudoType) 40 41 // Value will be cty.DynamicVal if either inference was impossible or 42 // if there was an error, leading to cty.DynamicPseudoType here. 43 return val.Type() 44 } 45 46 // analysisData is an implementation of lang.Data that returns unknown values 47 // of suitable types in order to achieve approximate dynamic analysis of 48 // expression result types, which we need for some upgrade rules. 49 // 50 // Unlike a usual implementation of this interface, this one never returns 51 // errors and will instead just return cty.DynamicVal if it can't produce 52 // an exact type for any reason. This can then allow partial upgrading to 53 // proceed and the caller can emit warning comments for ambiguous situations. 54 // 55 // N.B.: Source ranges in the data methods are meaningless, since they are 56 // just relative to the byte array passed to InferExpressionType, not to 57 // any real input file. 58 type analysisData struct { 59 an *analysis 60 } 61 62 var _ lang.Data = (*analysisData)(nil) 63 64 func (d analysisData) StaticValidateReferences(refs []*addrs.Reference, self addrs.Referenceable) tfdiags.Diagnostics { 65 // This implementation doesn't do any static validation. 66 return nil 67 } 68 69 func (d analysisData) GetCountAttr(addr addrs.CountAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 70 // All valid count attributes are numbers 71 return cty.UnknownVal(cty.Number), nil 72 } 73 74 func (d analysisData) GetForEachAttr(addr addrs.ForEachAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 75 return cty.DynamicVal, nil 76 } 77 78 func (d analysisData) GetResource(addr addrs.Resource, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 79 log.Printf("[TRACE] configupgrade: Determining type for %s", addr) 80 81 // Our analysis pass should've found a suitable schema for every resource 82 // type in the module. 83 providerType, ok := d.an.ResourceProviderType[addr] 84 if !ok { 85 // Should not be possible, since analysis visits every resource block. 86 log.Printf("[TRACE] configupgrade: analysis.GetResource doesn't have a provider type for %s", addr) 87 return cty.DynamicVal, nil 88 } 89 providerSchema, ok := d.an.ProviderSchemas[providerType] 90 if !ok { 91 // Should not be possible, since analysis loads schema for every provider. 92 log.Printf("[TRACE] configupgrade: analysis.GetResource doesn't have a provider schema for for %q", providerType) 93 return cty.DynamicVal, nil 94 } 95 schema, _ := providerSchema.SchemaForResourceAddr(addr) 96 if schema == nil { 97 // Should not be possible, since analysis loads schema for every provider. 98 log.Printf("[TRACE] configupgrade: analysis.GetResource doesn't have a schema for for %s", addr) 99 return cty.DynamicVal, nil 100 } 101 102 objTy := schema.ImpliedType() 103 104 // We'll emulate the normal evaluator's behavor of deciding whether to 105 // return a list or a single object type depending on whether count is 106 // set and whether an instance key is given in the address. 107 if d.an.ResourceHasCount[addr] { 108 log.Printf("[TRACE] configupgrade: %s refers to counted instance, so result is a list of %#v", addr, objTy) 109 return cty.UnknownVal(cty.List(objTy)), nil 110 } 111 112 log.Printf("[TRACE] configupgrade: %s refers to non-counted instance, so result is single object", addr) 113 return cty.UnknownVal(objTy), nil 114 } 115 116 func (d analysisData) GetLocalValue(addrs.LocalValue, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 117 // We can only predict these in general by recursively evaluating their 118 // expressions, which creates some undesirable complexity here so for 119 // now we'll just skip analyses with locals and see if this complexity 120 // is warranted with real-world testing. 121 return cty.DynamicVal, nil 122 } 123 124 func (d analysisData) GetModuleInstance(addrs.ModuleCallInstance, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 125 // We only work on one module at a time during upgrading, so we have no 126 // information about the outputs of a child module. 127 return cty.DynamicVal, nil 128 } 129 130 func (d analysisData) GetModuleInstanceOutput(addrs.ModuleCallOutput, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 131 // We only work on one module at a time during upgrading, so we have no 132 // information about the outputs of a child module. 133 return cty.DynamicVal, nil 134 } 135 136 func (d analysisData) GetPathAttr(addrs.PathAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 137 // All valid path attributes are strings 138 return cty.UnknownVal(cty.String), nil 139 } 140 141 func (d analysisData) GetTerraformAttr(addrs.TerraformAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 142 // All valid "terraform" attributes are strings 143 return cty.UnknownVal(cty.String), nil 144 } 145 146 func (d analysisData) GetInputVariable(addr addrs.InputVariable, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 147 // TODO: Collect shallow type information (list vs. map vs. string vs. unknown) 148 // in analysis and then return a similarly-approximate type here. 149 log.Printf("[TRACE] configupgrade: Determining type for %s", addr) 150 name := addr.Name 151 typeName := d.an.VariableTypes[name] 152 switch typeName { 153 case "list": 154 return cty.UnknownVal(cty.List(cty.DynamicPseudoType)), nil 155 case "map": 156 return cty.UnknownVal(cty.Map(cty.DynamicPseudoType)), nil 157 case "string": 158 return cty.UnknownVal(cty.String), nil 159 default: 160 return cty.DynamicVal, nil 161 } 162 }