github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/unparsed_value.go (about)

     1  package backend
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/hcl/v2"
     7  	"github.com/hashicorp/terraform/configs"
     8  	"github.com/hashicorp/terraform/terraform"
     9  	"github.com/hashicorp/terraform/tfdiags"
    10  	"github.com/zclconf/go-cty/cty"
    11  )
    12  
    13  // UnparsedVariableValue represents a variable value provided by the caller
    14  // whose parsing must be deferred until configuration is available.
    15  //
    16  // This exists to allow processing of variable-setting arguments (e.g. in the
    17  // command package) to be separated from parsing (in the backend package).
    18  type UnparsedVariableValue interface {
    19  	// ParseVariableValue information in the provided variable configuration
    20  	// to parse (if necessary) and return the variable value encapsulated in
    21  	// the receiver.
    22  	//
    23  	// If error diagnostics are returned, the resulting value may be invalid
    24  	// or incomplete.
    25  	ParseVariableValue(mode configs.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics)
    26  }
    27  
    28  // ParseVariableValues processes a map of unparsed variable values by
    29  // correlating each one with the given variable declarations which should
    30  // be from a root module.
    31  //
    32  // The map of unparsed variable values should include variables from all
    33  // possible root module declarations sources such that it is as complete as
    34  // it can possibly be for the current operation. If any declared variables
    35  // are not included in the map, ParseVariableValues will either substitute
    36  // a configured default value or produce an error.
    37  //
    38  // If this function returns without any errors in the diagnostics, the
    39  // resulting input values map is guaranteed to be valid and ready to pass
    40  // to terraform.NewContext. If the diagnostics contains errors, the returned
    41  // InputValues may be incomplete but will include the subset of variables
    42  // that were successfully processed, allowing for careful analysis of the
    43  // partial result.
    44  func ParseVariableValues(vv map[string]UnparsedVariableValue, decls map[string]*configs.Variable) (terraform.InputValues, tfdiags.Diagnostics) {
    45  	var diags tfdiags.Diagnostics
    46  	ret := make(terraform.InputValues, len(vv))
    47  
    48  	// Currently we're generating only warnings for undeclared variables
    49  	// defined in files (see below) but we only want to generate a few warnings
    50  	// at a time because existing deployments may have lots of these and
    51  	// the result can therefore be overwhelming.
    52  	seenUndeclaredInFile := 0
    53  
    54  	for name, rv := range vv {
    55  		var mode configs.VariableParsingMode
    56  		config, declared := decls[name]
    57  		if declared {
    58  			mode = config.ParsingMode
    59  		} else {
    60  			mode = configs.VariableParseLiteral
    61  		}
    62  
    63  		val, valDiags := rv.ParseVariableValue(mode)
    64  		diags = diags.Append(valDiags)
    65  		if valDiags.HasErrors() {
    66  			continue
    67  		}
    68  
    69  		if !declared {
    70  			switch val.SourceType {
    71  			case terraform.ValueFromConfig, terraform.ValueFromAutoFile, terraform.ValueFromNamedFile:
    72  				// These source types have source ranges, so we can produce
    73  				// a nice error message with good context.
    74  				//
    75  				// This one is a warning for now because there is an existing
    76  				// pattern of providing a file containing the superset of
    77  				// variables across all configurations in an organization. This
    78  				// is deprecated in v0.12.0 because it's more important to give
    79  				// feedback to users who make typos. Those using this approach
    80  				// should migrate to using environment variables instead before
    81  				// this becomes an error in a future major release.
    82  				if seenUndeclaredInFile < 3 {
    83  					diags = diags.Append(tfdiags.Sourceless(
    84  						tfdiags.Warning,
    85  						"Value for undeclared variable",
    86  						fmt.Sprintf("The root module does not declare a variable named %q but a value was found in file %q. To use this value, add a \"variable\" block to the configuration.\n\nUsing a variables file to set an undeclared variable is deprecated and will become an error in a future release. If you wish to provide certain \"global\" settings to all configurations in your organization, use TF_VAR_... environment variables to set these instead.", name, val.SourceRange.Filename),
    87  					))
    88  				}
    89  				seenUndeclaredInFile++
    90  
    91  			case terraform.ValueFromEnvVar:
    92  				// We allow and ignore undeclared names for environment
    93  				// variables, because users will often set these globally
    94  				// when they are used across many (but not necessarily all)
    95  				// configurations.
    96  			case terraform.ValueFromCLIArg:
    97  				diags = diags.Append(tfdiags.Sourceless(
    98  					tfdiags.Error,
    99  					"Value for undeclared variable",
   100  					fmt.Sprintf("A variable named %q was assigned on the command line, but the root module does not declare a variable of that name. To use this value, add a \"variable\" block to the configuration.", name),
   101  				))
   102  			default:
   103  				// For all other source types we are more vague, but other situations
   104  				// don't generally crop up at this layer in practice.
   105  				diags = diags.Append(tfdiags.Sourceless(
   106  					tfdiags.Error,
   107  					"Value for undeclared variable",
   108  					fmt.Sprintf("A variable named %q was assigned a value, but the root module does not declare a variable of that name. To use this value, add a \"variable\" block to the configuration.", name),
   109  				))
   110  			}
   111  			continue
   112  		}
   113  
   114  		ret[name] = val
   115  	}
   116  
   117  	if seenUndeclaredInFile >= 3 {
   118  		extras := seenUndeclaredInFile - 2
   119  		diags = diags.Append(&hcl.Diagnostic{
   120  			Severity: hcl.DiagWarning,
   121  			Summary:  "Values for undeclared variables",
   122  			Detail:   fmt.Sprintf("In addition to the other similar warnings shown, %d other variable(s) defined without being declared.", extras),
   123  		})
   124  	}
   125  
   126  	// By this point we should've gathered all of the required root module
   127  	// variables from one of the many possible sources. We'll now populate
   128  	// any we haven't gathered as their defaults and fail if any of the
   129  	// missing ones are required.
   130  	for name, vc := range decls {
   131  		if _, defined := ret[name]; defined {
   132  			continue
   133  		}
   134  
   135  		if vc.Required() {
   136  			diags = diags.Append(&hcl.Diagnostic{
   137  				Severity: hcl.DiagError,
   138  				Summary:  "No value for required variable",
   139  				Detail:   fmt.Sprintf("The root module input variable %q is not set, and has no default value. Use a -var or -var-file command line argument to provide a value for this variable.", name),
   140  				Subject:  vc.DeclRange.Ptr(),
   141  			})
   142  
   143  			// We'll include a placeholder value anyway, just so that our
   144  			// result is complete for any calling code that wants to cautiously
   145  			// analyze it for diagnostic purposes. Since our diagnostics now
   146  			// includes an error, normal processing will ignore this result.
   147  			ret[name] = &terraform.InputValue{
   148  				Value:       cty.DynamicVal,
   149  				SourceType:  terraform.ValueFromConfig,
   150  				SourceRange: tfdiags.SourceRangeFromHCL(vc.DeclRange),
   151  			}
   152  		} else {
   153  			ret[name] = &terraform.InputValue{
   154  				Value:       vc.Default,
   155  				SourceType:  terraform.ValueFromConfig,
   156  				SourceRange: tfdiags.SourceRangeFromHCL(vc.DeclRange),
   157  			}
   158  		}
   159  	}
   160  
   161  	return ret, diags
   162  }