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