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  }