github.com/hugorut/terraform@v1.1.3/src/backend/unparsed_value.go (about) 1 package backend 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl/v2" 7 "github.com/hugorut/terraform/src/configs" 8 "github.com/hugorut/terraform/src/terraform" 9 "github.com/hugorut/terraform/src/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 // ParseUndeclaredVariableValues processes a map of unparsed variable values 29 // and returns an input values map of the ones not declared in the specified 30 // declaration map along with detailed diagnostics about values of undeclared 31 // variables being present, depending on the source of these values. If more 32 // than two undeclared values are present in file form (config, auto, -var-file) 33 // the remaining errors are summarized to avoid a massive list of errors. 34 func ParseUndeclaredVariableValues(vv map[string]UnparsedVariableValue, decls map[string]*configs.Variable) (terraform.InputValues, tfdiags.Diagnostics) { 35 var diags tfdiags.Diagnostics 36 ret := make(terraform.InputValues, len(vv)) 37 seenUndeclaredInFile := 0 38 39 for name, rv := range vv { 40 if _, declared := decls[name]; declared { 41 // Only interested in parsing undeclared variables 42 continue 43 } 44 45 val, valDiags := rv.ParseVariableValue(configs.VariableParseLiteral) 46 if valDiags.HasErrors() { 47 continue 48 } 49 50 ret[name] = val 51 52 switch val.SourceType { 53 case terraform.ValueFromConfig, terraform.ValueFromAutoFile, terraform.ValueFromNamedFile: 54 // We allow undeclared names for variable values from files and warn in case 55 // users have forgotten a variable {} declaration or have a typo in their var name. 56 // Some users will actively ignore this warning because they use a .tfvars file 57 // across multiple configurations. 58 if seenUndeclaredInFile < 2 { 59 diags = diags.Append(tfdiags.Sourceless( 60 tfdiags.Warning, 61 "Value for undeclared variable", 62 fmt.Sprintf("The root module does not declare a variable named %q but a value was found in file %q. If you meant to use this value, add a \"variable\" block to the configuration.\n\nTo silence these warnings, use TF_VAR_... environment variables to provide certain \"global\" settings to all configurations in your organization. To reduce the verbosity of these warnings, use the -compact-warnings option.", name, val.SourceRange.Filename), 63 )) 64 } 65 seenUndeclaredInFile++ 66 67 case terraform.ValueFromEnvVar: 68 // We allow and ignore undeclared names for environment 69 // variables, because users will often set these globally 70 // when they are used across many (but not necessarily all) 71 // configurations. 72 case terraform.ValueFromCLIArg: 73 diags = diags.Append(tfdiags.Sourceless( 74 tfdiags.Error, 75 "Value for undeclared variable", 76 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), 77 )) 78 default: 79 // For all other source types we are more vague, but other situations 80 // don't generally crop up at this layer in practice. 81 diags = diags.Append(tfdiags.Sourceless( 82 tfdiags.Error, 83 "Value for undeclared variable", 84 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), 85 )) 86 } 87 } 88 89 if seenUndeclaredInFile > 2 { 90 extras := seenUndeclaredInFile - 2 91 diags = diags.Append(&hcl.Diagnostic{ 92 Severity: hcl.DiagWarning, 93 Summary: "Values for undeclared variables", 94 Detail: fmt.Sprintf("In addition to the other similar warnings shown, %d other variable(s) defined without being declared.", extras), 95 }) 96 } 97 98 return ret, diags 99 } 100 101 // ParseDeclaredVariableValues processes a map of unparsed variable values 102 // and returns an input values map of the ones declared in the specified 103 // variable declaration mapping. Diagnostics will be populating with 104 // any variable parsing errors encountered within this collection. 105 func ParseDeclaredVariableValues(vv map[string]UnparsedVariableValue, decls map[string]*configs.Variable) (terraform.InputValues, tfdiags.Diagnostics) { 106 var diags tfdiags.Diagnostics 107 ret := make(terraform.InputValues, len(vv)) 108 109 for name, rv := range vv { 110 var mode configs.VariableParsingMode 111 config, declared := decls[name] 112 113 if declared { 114 mode = config.ParsingMode 115 } else { 116 // Only interested in parsing declared variables 117 continue 118 } 119 120 val, valDiags := rv.ParseVariableValue(mode) 121 diags = diags.Append(valDiags) 122 if valDiags.HasErrors() { 123 continue 124 } 125 126 ret[name] = val 127 } 128 129 return ret, diags 130 } 131 132 // Checks all given terraform.InputValues variable maps for the existance of 133 // a named variable 134 func isDefinedAny(name string, maps ...terraform.InputValues) bool { 135 for _, m := range maps { 136 if _, defined := m[name]; defined { 137 return true 138 } 139 } 140 return false 141 } 142 143 // ParseVariableValues processes a map of unparsed variable values by 144 // correlating each one with the given variable declarations which should 145 // be from a root module. 146 // 147 // The map of unparsed variable values should include variables from all 148 // possible root module declarations sources such that it is as complete as 149 // it can possibly be for the current operation. If any declared variables 150 // are not included in the map, ParseVariableValues will either substitute 151 // a configured default value or produce an error. 152 // 153 // If this function returns without any errors in the diagnostics, the 154 // resulting input values map is guaranteed to be valid and ready to pass 155 // to terraform.NewContext. If the diagnostics contains errors, the returned 156 // InputValues may be incomplete but will include the subset of variables 157 // that were successfully processed, allowing for careful analysis of the 158 // partial result. 159 func ParseVariableValues(vv map[string]UnparsedVariableValue, decls map[string]*configs.Variable) (terraform.InputValues, tfdiags.Diagnostics) { 160 ret, diags := ParseDeclaredVariableValues(vv, decls) 161 undeclared, diagsUndeclared := ParseUndeclaredVariableValues(vv, decls) 162 163 diags = diags.Append(diagsUndeclared) 164 165 // By this point we should've gathered all of the required root module 166 // variables from one of the many possible sources. We'll now populate 167 // any we haven't gathered as their defaults and fail if any of the 168 // missing ones are required. 169 for name, vc := range decls { 170 if isDefinedAny(name, ret, undeclared) { 171 continue 172 } 173 174 if vc.Required() { 175 diags = diags.Append(&hcl.Diagnostic{ 176 Severity: hcl.DiagError, 177 Summary: "No value for required variable", 178 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), 179 Subject: vc.DeclRange.Ptr(), 180 }) 181 182 // We'll include a placeholder value anyway, just so that our 183 // result is complete for any calling code that wants to cautiously 184 // analyze it for diagnostic purposes. Since our diagnostics now 185 // includes an error, normal processing will ignore this result. 186 ret[name] = &terraform.InputValue{ 187 Value: cty.DynamicVal, 188 SourceType: terraform.ValueFromConfig, 189 SourceRange: tfdiags.SourceRangeFromHCL(vc.DeclRange), 190 } 191 } else { 192 ret[name] = &terraform.InputValue{ 193 Value: vc.Default, 194 SourceType: terraform.ValueFromConfig, 195 SourceRange: tfdiags.SourceRangeFromHCL(vc.DeclRange), 196 } 197 } 198 } 199 200 return ret, diags 201 }