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