]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
bae9f6d2 | 5 | |
107c1cdb ND |
6 | "github.com/hashicorp/hcl2/hcl" |
7 | "github.com/zclconf/go-cty/cty" | |
8 | "github.com/zclconf/go-cty/cty/convert" | |
9 | ||
10 | "github.com/hashicorp/terraform/configs" | |
11 | "github.com/hashicorp/terraform/tfdiags" | |
12 | ) | |
13 | ||
14 | // InputValue represents a value for a variable in the root module, provided | |
15 | // as part of the definition of an operation. | |
16 | type InputValue struct { | |
17 | Value cty.Value | |
18 | SourceType ValueSourceType | |
19 | ||
20 | // SourceRange provides source location information for values whose | |
21 | // SourceType is either ValueFromConfig or ValueFromFile. It is not | |
22 | // populated for other source types, and so should not be used. | |
23 | SourceRange tfdiags.SourceRange | |
24 | } | |
25 | ||
26 | // ValueSourceType describes what broad category of source location provided | |
27 | // a particular value. | |
28 | type ValueSourceType rune | |
29 | ||
30 | const ( | |
31 | // ValueFromUnknown is the zero value of ValueSourceType and is not valid. | |
32 | ValueFromUnknown ValueSourceType = 0 | |
33 | ||
34 | // ValueFromConfig indicates that a value came from a .tf or .tf.json file, | |
35 | // e.g. the default value defined for a variable. | |
36 | ValueFromConfig ValueSourceType = 'C' | |
37 | ||
38 | // ValueFromAutoFile indicates that a value came from a "values file", like | |
39 | // a .tfvars file, that was implicitly loaded by naming convention. | |
40 | ValueFromAutoFile ValueSourceType = 'F' | |
41 | ||
42 | // ValueFromNamedFile indicates that a value came from a named "values file", | |
43 | // like a .tfvars file, that was passed explicitly on the command line (e.g. | |
44 | // -var-file=foo.tfvars). | |
45 | ValueFromNamedFile ValueSourceType = 'N' | |
46 | ||
47 | // ValueFromCLIArg indicates that the value was provided directly in | |
48 | // a CLI argument. The name of this argument is not recorded and so it must | |
49 | // be inferred from context. | |
50 | ValueFromCLIArg ValueSourceType = 'A' | |
51 | ||
52 | // ValueFromEnvVar indicates that the value was provided via an environment | |
53 | // variable. The name of the variable is not recorded and so it must be | |
54 | // inferred from context. | |
55 | ValueFromEnvVar ValueSourceType = 'E' | |
56 | ||
57 | // ValueFromInput indicates that the value was provided at an interactive | |
58 | // input prompt. | |
59 | ValueFromInput ValueSourceType = 'I' | |
60 | ||
61 | // ValueFromPlan indicates that the value was retrieved from a stored plan. | |
62 | ValueFromPlan ValueSourceType = 'P' | |
63 | ||
64 | // ValueFromCaller indicates that the value was explicitly overridden by | |
65 | // a caller to Context.SetVariable after the context was constructed. | |
66 | ValueFromCaller ValueSourceType = 'S' | |
bae9f6d2 JC |
67 | ) |
68 | ||
107c1cdb ND |
69 | func (v *InputValue) GoString() string { |
70 | if (v.SourceRange != tfdiags.SourceRange{}) { | |
71 | return fmt.Sprintf("&terraform.InputValue{Value: %#v, SourceType: %#v, SourceRange: %#v}", v.Value, v.SourceType, v.SourceRange) | |
72 | } else { | |
73 | return fmt.Sprintf("&terraform.InputValue{Value: %#v, SourceType: %#v}", v.Value, v.SourceType) | |
74 | } | |
75 | } | |
76 | ||
77 | func (v ValueSourceType) GoString() string { | |
78 | return fmt.Sprintf("terraform.%s", v) | |
79 | } | |
80 | ||
81 | //go:generate stringer -type ValueSourceType | |
82 | ||
83 | // InputValues is a map of InputValue instances. | |
84 | type InputValues map[string]*InputValue | |
85 | ||
86 | // InputValuesFromCaller turns the given map of naked values into an | |
87 | // InputValues that attributes each value to "a caller", using the source | |
88 | // type ValueFromCaller. This is primarily useful for testing purposes. | |
bae9f6d2 | 89 | // |
107c1cdb ND |
90 | // This should not be used as a general way to convert map[string]cty.Value |
91 | // into InputValues, since in most real cases we want to set a suitable | |
92 | // other SourceType and possibly SourceRange value. | |
93 | func InputValuesFromCaller(vals map[string]cty.Value) InputValues { | |
94 | ret := make(InputValues, len(vals)) | |
95 | for k, v := range vals { | |
96 | ret[k] = &InputValue{ | |
97 | Value: v, | |
98 | SourceType: ValueFromCaller, | |
bae9f6d2 | 99 | } |
107c1cdb ND |
100 | } |
101 | return ret | |
102 | } | |
bae9f6d2 | 103 | |
107c1cdb ND |
104 | // Override merges the given value maps with the receiver, overriding any |
105 | // conflicting keys so that the latest definition wins. | |
106 | func (vv InputValues) Override(others ...InputValues) InputValues { | |
107 | // FIXME: This should check to see if any of the values are maps and | |
108 | // merge them if so, in order to preserve the behavior from prior to | |
109 | // Terraform 0.12. | |
110 | ret := make(InputValues) | |
111 | for k, v := range vv { | |
112 | ret[k] = v | |
113 | } | |
114 | for _, other := range others { | |
115 | for k, v := range other { | |
116 | ret[k] = v | |
bae9f6d2 | 117 | } |
107c1cdb ND |
118 | } |
119 | return ret | |
120 | } | |
bae9f6d2 | 121 | |
107c1cdb ND |
122 | // JustValues returns a map that just includes the values, discarding the |
123 | // source information. | |
124 | func (vv InputValues) JustValues() map[string]cty.Value { | |
125 | ret := make(map[string]cty.Value, len(vv)) | |
126 | for k, v := range vv { | |
127 | ret[k] = v.Value | |
bae9f6d2 | 128 | } |
107c1cdb ND |
129 | return ret |
130 | } | |
bae9f6d2 | 131 | |
107c1cdb ND |
132 | // DefaultVariableValues returns an InputValues map representing the default |
133 | // values specified for variables in the given configuration map. | |
134 | func DefaultVariableValues(configs map[string]*configs.Variable) InputValues { | |
135 | ret := make(InputValues) | |
136 | for k, c := range configs { | |
137 | if c.Default == cty.NilVal { | |
bae9f6d2 JC |
138 | continue |
139 | } | |
107c1cdb ND |
140 | ret[k] = &InputValue{ |
141 | Value: c.Default, | |
142 | SourceType: ValueFromConfig, | |
143 | SourceRange: tfdiags.SourceRangeFromHCL(c.DeclRange), | |
144 | } | |
145 | } | |
146 | return ret | |
147 | } | |
bae9f6d2 | 148 | |
107c1cdb ND |
149 | // SameValues returns true if the given InputValues has the same values as |
150 | // the receiever, disregarding the source types and source ranges. | |
151 | // | |
152 | // Values are compared using the cty "RawEquals" method, which means that | |
153 | // unknown values can be considered equal to one another if they are of the | |
154 | // same type. | |
155 | func (vv InputValues) SameValues(other InputValues) bool { | |
156 | if len(vv) != len(other) { | |
157 | return false | |
158 | } | |
bae9f6d2 | 159 | |
107c1cdb ND |
160 | for k, v := range vv { |
161 | ov, exists := other[k] | |
162 | if !exists { | |
163 | return false | |
164 | } | |
165 | if !v.Value.RawEquals(ov.Value) { | |
166 | return false | |
bae9f6d2 JC |
167 | } |
168 | } | |
169 | ||
107c1cdb ND |
170 | return true |
171 | } | |
bae9f6d2 | 172 | |
107c1cdb ND |
173 | // HasValues returns true if the reciever has the same values as in the given |
174 | // map, disregarding the source types and source ranges. | |
175 | // | |
176 | // Values are compared using the cty "RawEquals" method, which means that | |
177 | // unknown values can be considered equal to one another if they are of the | |
178 | // same type. | |
179 | func (vv InputValues) HasValues(vals map[string]cty.Value) bool { | |
180 | if len(vv) != len(vals) { | |
181 | return false | |
182 | } | |
183 | ||
184 | for k, v := range vv { | |
185 | oVal, exists := vals[k] | |
186 | if !exists { | |
187 | return false | |
188 | } | |
189 | if !v.Value.RawEquals(oVal) { | |
190 | return false | |
bae9f6d2 JC |
191 | } |
192 | } | |
193 | ||
107c1cdb | 194 | return true |
bae9f6d2 JC |
195 | } |
196 | ||
107c1cdb ND |
197 | // Identical returns true if the given InputValues has the same values, |
198 | // source types, and source ranges as the receiver. | |
199 | // | |
200 | // Values are compared using the cty "RawEquals" method, which means that | |
201 | // unknown values can be considered equal to one another if they are of the | |
202 | // same type. | |
203 | // | |
204 | // This method is primarily for testing. For most practical purposes, it's | |
205 | // better to use SameValues or HasValues. | |
206 | func (vv InputValues) Identical(other InputValues) bool { | |
207 | if len(vv) != len(other) { | |
208 | return false | |
bae9f6d2 JC |
209 | } |
210 | ||
107c1cdb ND |
211 | for k, v := range vv { |
212 | ov, exists := other[k] | |
213 | if !exists { | |
214 | return false | |
215 | } | |
216 | if !v.Value.RawEquals(ov.Value) { | |
217 | return false | |
218 | } | |
219 | if v.SourceType != ov.SourceType { | |
220 | return false | |
221 | } | |
222 | if v.SourceRange != ov.SourceRange { | |
223 | return false | |
224 | } | |
bae9f6d2 JC |
225 | } |
226 | ||
107c1cdb ND |
227 | return true |
228 | } | |
229 | ||
230 | // checkInputVariables ensures that variable values supplied at the UI conform | |
231 | // to their corresponding declarations in configuration. | |
232 | // | |
233 | // The set of values is considered valid only if the returned diagnostics | |
234 | // does not contain errors. A valid set of values may still produce warnings, | |
235 | // which should be returned to the user. | |
236 | func checkInputVariables(vcs map[string]*configs.Variable, vs InputValues) tfdiags.Diagnostics { | |
237 | var diags tfdiags.Diagnostics | |
238 | ||
239 | for name, vc := range vcs { | |
240 | val, isSet := vs[name] | |
241 | if !isSet { | |
242 | // Always an error, since the caller should already have included | |
243 | // default values from the configuration in the values map. | |
244 | diags = diags.Append(tfdiags.Sourceless( | |
245 | tfdiags.Error, | |
246 | "Unassigned variable", | |
247 | 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), | |
248 | )) | |
249 | continue | |
bae9f6d2 | 250 | } |
107c1cdb ND |
251 | |
252 | wantType := vc.Type | |
253 | ||
254 | // A given value is valid if it can convert to the desired type. | |
255 | _, err := convert.Convert(val.Value, wantType) | |
256 | if err != nil { | |
257 | switch val.SourceType { | |
258 | case ValueFromConfig, ValueFromAutoFile, ValueFromNamedFile: | |
259 | // We have source location information for these. | |
260 | diags = diags.Append(&hcl.Diagnostic{ | |
261 | Severity: hcl.DiagError, | |
262 | Summary: "Invalid value for input variable", | |
263 | Detail: fmt.Sprintf("The given value is not valid for variable %q: %s.", name, err), | |
264 | Subject: val.SourceRange.ToHCL().Ptr(), | |
265 | }) | |
266 | case ValueFromEnvVar: | |
267 | diags = diags.Append(tfdiags.Sourceless( | |
268 | tfdiags.Error, | |
269 | "Invalid value for input variable", | |
270 | fmt.Sprintf("The environment variable TF_VAR_%s does not contain a valid value for variable %q: %s.", name, name, err), | |
271 | )) | |
272 | case ValueFromCLIArg: | |
273 | diags = diags.Append(tfdiags.Sourceless( | |
274 | tfdiags.Error, | |
275 | "Invalid value for input variable", | |
276 | fmt.Sprintf("The argument -var=\"%s=...\" does not contain a valid value for variable %q: %s.", name, name, err), | |
277 | )) | |
278 | case ValueFromInput: | |
279 | diags = diags.Append(tfdiags.Sourceless( | |
280 | tfdiags.Error, | |
281 | "Invalid value for input variable", | |
282 | fmt.Sprintf("The value entered for variable %q is not valid: %s.", name, err), | |
283 | )) | |
284 | default: | |
285 | // The above gets us good coverage for the situations users | |
286 | // are likely to encounter with their own inputs. The other | |
287 | // cases are generally implementation bugs, so we'll just | |
288 | // use a generic error for these. | |
289 | diags = diags.Append(tfdiags.Sourceless( | |
290 | tfdiags.Error, | |
291 | "Invalid value for input variable", | |
292 | fmt.Sprintf("The value provided for variable %q is not valid: %s.", name, err), | |
293 | )) | |
294 | } | |
bae9f6d2 | 295 | } |
bae9f6d2 | 296 | } |
107c1cdb ND |
297 | |
298 | // Check for any variables that are assigned without being configured. | |
299 | // This is always an implementation error in the caller, because we | |
300 | // expect undefined variables to be caught during context construction | |
301 | // where there is better context to report it well. | |
302 | for name := range vs { | |
303 | if _, defined := vcs[name]; !defined { | |
304 | diags = diags.Append(tfdiags.Sourceless( | |
305 | tfdiags.Error, | |
306 | "Value assigned to undeclared variable", | |
307 | fmt.Sprintf("A value was assigned to an undeclared input variable %q.", name), | |
308 | )) | |
309 | } | |
310 | } | |
311 | ||
312 | return diags | |
bae9f6d2 | 313 | } |