github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/terraform/variables.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 6 "github.com/zclconf/go-cty/cty" 7 8 "github.com/hashicorp/terraform/internal/configs" 9 "github.com/hashicorp/terraform/internal/tfdiags" 10 ) 11 12 // InputValue represents a raw value for a root module input variable as 13 // provided by the external caller into a function like terraform.Context.Plan. 14 // 15 // InputValue should represent as directly as possible what the user set the 16 // variable to, without any attempt to convert the value to the variable's 17 // type constraint or substitute the configured default values for variables 18 // that wasn't set. Those adjustments will be handled by Terraform Core itself 19 // as part of performing the requested operation. 20 // 21 // A Terraform Core caller must provide an InputValue object for each of the 22 // variables declared in the root module, even if the end user didn't provide 23 // an explicit value for some of them. See the Value field documentation for 24 // how to handle that situation. 25 // 26 // Terraform Core also internally uses InputValue to represent the raw value 27 // provided for a variable in a child module call, following the same 28 // conventions. However, that's an implementation detail not visible to 29 // outside callers. 30 type InputValue struct { 31 // Value is the raw value as provided by the user as part of the plan 32 // options, or a corresponding similar data structure for non-plan 33 // operations. 34 // 35 // If a particular variable declared in the root module is _not_ set by 36 // the user then the caller must still provide an InputValue for it but 37 // must set Value to cty.NilVal to represent the absense of a value. 38 // This requirement is to help detect situations where the caller isn't 39 // correctly detecting and handling all of the declared variables. 40 // 41 // For historical reasons it's important that callers distinguish the 42 // situation of the value not being set at all (cty.NilVal) from the 43 // situation of it being explicitly set to null (a cty.NullVal result): 44 // for "nullable" input variables that distinction unfortunately decides 45 // whether the final value will be the variable's default or will be 46 // explicitly null. 47 Value cty.Value 48 49 // SourceType is a high-level category for where the value of Value 50 // came from, which Terraform Core uses to tailor some of its error 51 // messages to be more helpful to the user. 52 // 53 // Some SourceType values should be accompanied by a populated SourceRange 54 // value. See that field's documentation below for more information. 55 SourceType ValueSourceType 56 57 // SourceRange provides source location information for values whose 58 // SourceType is either ValueFromConfig, ValueFromNamedFile, or 59 // ValueForNormalFile. It is not populated for other source types, and so 60 // should not be used. 61 SourceRange tfdiags.SourceRange 62 } 63 64 // ValueSourceType describes what broad category of source location provided 65 // a particular value. 66 type ValueSourceType rune 67 68 const ( 69 // ValueFromUnknown is the zero value of ValueSourceType and is not valid. 70 ValueFromUnknown ValueSourceType = 0 71 72 // ValueFromConfig indicates that a value came from a .tf or .tf.json file, 73 // e.g. the default value defined for a variable. 74 ValueFromConfig ValueSourceType = 'C' 75 76 // ValueFromAutoFile indicates that a value came from a "values file", like 77 // a .tfvars file, that was implicitly loaded by naming convention. 78 ValueFromAutoFile ValueSourceType = 'F' 79 80 // ValueFromNamedFile indicates that a value came from a named "values file", 81 // like a .tfvars file, that was passed explicitly on the command line (e.g. 82 // -var-file=foo.tfvars). 83 ValueFromNamedFile ValueSourceType = 'N' 84 85 // ValueFromCLIArg indicates that the value was provided directly in 86 // a CLI argument. The name of this argument is not recorded and so it must 87 // be inferred from context. 88 ValueFromCLIArg ValueSourceType = 'A' 89 90 // ValueFromEnvVar indicates that the value was provided via an environment 91 // variable. The name of the variable is not recorded and so it must be 92 // inferred from context. 93 ValueFromEnvVar ValueSourceType = 'E' 94 95 // ValueFromInput indicates that the value was provided at an interactive 96 // input prompt. 97 ValueFromInput ValueSourceType = 'I' 98 99 // ValueFromPlan indicates that the value was retrieved from a stored plan. 100 ValueFromPlan ValueSourceType = 'P' 101 102 // ValueFromCaller indicates that the value was explicitly overridden by 103 // a caller to Context.SetVariable after the context was constructed. 104 ValueFromCaller ValueSourceType = 'S' 105 ) 106 107 func (v *InputValue) GoString() string { 108 if (v.SourceRange != tfdiags.SourceRange{}) { 109 return fmt.Sprintf("&terraform.InputValue{Value: %#v, SourceType: %#v, SourceRange: %#v}", v.Value, v.SourceType, v.SourceRange) 110 } else { 111 return fmt.Sprintf("&terraform.InputValue{Value: %#v, SourceType: %#v}", v.Value, v.SourceType) 112 } 113 } 114 115 // HasSourceRange returns true if the reciever has a source type for which 116 // we expect the SourceRange field to be populated with a valid range. 117 func (v *InputValue) HasSourceRange() bool { 118 return v.SourceType.HasSourceRange() 119 } 120 121 // HasSourceRange returns true if the reciever is one of the source types 122 // that is used along with a valid SourceRange field when appearing inside an 123 // InputValue object. 124 func (v ValueSourceType) HasSourceRange() bool { 125 switch v { 126 case ValueFromConfig, ValueFromAutoFile, ValueFromNamedFile: 127 return true 128 default: 129 return false 130 } 131 } 132 133 func (v ValueSourceType) GoString() string { 134 return fmt.Sprintf("terraform.%s", v) 135 } 136 137 //go:generate go run golang.org/x/tools/cmd/stringer -type ValueSourceType 138 139 // InputValues is a map of InputValue instances. 140 type InputValues map[string]*InputValue 141 142 // InputValuesFromCaller turns the given map of naked values into an 143 // InputValues that attributes each value to "a caller", using the source 144 // type ValueFromCaller. This is primarily useful for testing purposes. 145 // 146 // This should not be used as a general way to convert map[string]cty.Value 147 // into InputValues, since in most real cases we want to set a suitable 148 // other SourceType and possibly SourceRange value. 149 func InputValuesFromCaller(vals map[string]cty.Value) InputValues { 150 ret := make(InputValues, len(vals)) 151 for k, v := range vals { 152 ret[k] = &InputValue{ 153 Value: v, 154 SourceType: ValueFromCaller, 155 } 156 } 157 return ret 158 } 159 160 // Override merges the given value maps with the receiver, overriding any 161 // conflicting keys so that the latest definition wins. 162 func (vv InputValues) Override(others ...InputValues) InputValues { 163 // FIXME: This should check to see if any of the values are maps and 164 // merge them if so, in order to preserve the behavior from prior to 165 // Terraform 0.12. 166 ret := make(InputValues) 167 for k, v := range vv { 168 ret[k] = v 169 } 170 for _, other := range others { 171 for k, v := range other { 172 ret[k] = v 173 } 174 } 175 return ret 176 } 177 178 // JustValues returns a map that just includes the values, discarding the 179 // source information. 180 func (vv InputValues) JustValues() map[string]cty.Value { 181 ret := make(map[string]cty.Value, len(vv)) 182 for k, v := range vv { 183 ret[k] = v.Value 184 } 185 return ret 186 } 187 188 // SameValues returns true if the given InputValues has the same values as 189 // the receiever, disregarding the source types and source ranges. 190 // 191 // Values are compared using the cty "RawEquals" method, which means that 192 // unknown values can be considered equal to one another if they are of the 193 // same type. 194 func (vv InputValues) SameValues(other InputValues) bool { 195 if len(vv) != len(other) { 196 return false 197 } 198 199 for k, v := range vv { 200 ov, exists := other[k] 201 if !exists { 202 return false 203 } 204 if !v.Value.RawEquals(ov.Value) { 205 return false 206 } 207 } 208 209 return true 210 } 211 212 // HasValues returns true if the reciever has the same values as in the given 213 // map, disregarding the source types and source ranges. 214 // 215 // Values are compared using the cty "RawEquals" method, which means that 216 // unknown values can be considered equal to one another if they are of the 217 // same type. 218 func (vv InputValues) HasValues(vals map[string]cty.Value) bool { 219 if len(vv) != len(vals) { 220 return false 221 } 222 223 for k, v := range vv { 224 oVal, exists := vals[k] 225 if !exists { 226 return false 227 } 228 if !v.Value.RawEquals(oVal) { 229 return false 230 } 231 } 232 233 return true 234 } 235 236 // Identical returns true if the given InputValues has the same values, 237 // source types, and source ranges as the receiver. 238 // 239 // Values are compared using the cty "RawEquals" method, which means that 240 // unknown values can be considered equal to one another if they are of the 241 // same type. 242 // 243 // This method is primarily for testing. For most practical purposes, it's 244 // better to use SameValues or HasValues. 245 func (vv InputValues) Identical(other InputValues) bool { 246 if len(vv) != len(other) { 247 return false 248 } 249 250 for k, v := range vv { 251 ov, exists := other[k] 252 if !exists { 253 return false 254 } 255 if !v.Value.RawEquals(ov.Value) { 256 return false 257 } 258 if v.SourceType != ov.SourceType { 259 return false 260 } 261 if v.SourceRange != ov.SourceRange { 262 return false 263 } 264 } 265 266 return true 267 } 268 269 // checkInputVariables ensures that the caller provided an InputValue 270 // definition for each root module variable declared in the configuration. 271 // The caller must provide an InputVariables with keys exactly matching 272 // the declared variables, though some of them may be marked explicitly 273 // unset by their values being cty.NilVal. 274 // 275 // This doesn't perform any type checking, default value substitution, or 276 // validation checks. Those are all handled during a graph walk when we 277 // visit the graph nodes representing each root variable. 278 // 279 // The set of values is considered valid only if the returned diagnostics 280 // does not contain errors. A valid set of values may still produce warnings, 281 // which should be returned to the user. 282 func checkInputVariables(vcs map[string]*configs.Variable, vs InputValues) tfdiags.Diagnostics { 283 var diags tfdiags.Diagnostics 284 285 for name := range vcs { 286 _, isSet := vs[name] 287 if !isSet { 288 // Always an error, since the caller should have produced an 289 // item with Value: cty.NilVal to be explicit that it offered 290 // an opportunity to set this variable. 291 diags = diags.Append(tfdiags.Sourceless( 292 tfdiags.Error, 293 "Unassigned variable", 294 fmt.Sprintf("The input variable %q has not been assigned a value. This is a bug in Terraform; please report it in a GitHub issue.", name), 295 )) 296 continue 297 } 298 } 299 300 // Check for any variables that are assigned without being configured. 301 // This is always an implementation error in the caller, because we 302 // expect undefined variables to be caught during context construction 303 // where there is better context to report it well. 304 for name := range vs { 305 if _, defined := vcs[name]; !defined { 306 diags = diags.Append(tfdiags.Sourceless( 307 tfdiags.Error, 308 "Value assigned to undeclared variable", 309 fmt.Sprintf("A value was assigned to an undeclared input variable %q.", name), 310 )) 311 } 312 } 313 314 return diags 315 }