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 }