forked from hashicorp/terraform
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmeta_vars.go
257 lines (228 loc) · 8.13 KB
/
meta_vars.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
package command
import (
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
hcljson "github.com/hashicorp/hcl/v2/json"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
)
// VarEnvPrefix is the prefix for environment variables that represent values
// for root module input variables.
const VarEnvPrefix = "TF_VAR_"
// collectVariableValues inspects the various places that root module input variable
// values can come from and constructs a map ready to be passed to the
// backend as part of a backend.Operation.
//
// This method returns diagnostics relating to the collection of the values,
// but the values themselves may produce additional diagnostics when finally
// parsed.
func (m *Meta) collectVariableValues() (map[string]backend.UnparsedVariableValue, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
ret := map[string]backend.UnparsedVariableValue{}
// First we'll deal with environment variables, since they have the lowest
// precedence.
{
env := os.Environ()
for _, raw := range env {
if !strings.HasPrefix(raw, VarEnvPrefix) {
continue
}
raw = raw[len(VarEnvPrefix):] // trim the prefix
eq := strings.Index(raw, "=")
if eq == -1 {
// Seems invalid, so we'll ignore it.
continue
}
name := raw[:eq]
rawVal := raw[eq+1:]
ret[name] = unparsedVariableValueString{
str: rawVal,
name: name,
sourceType: terraform.ValueFromEnvVar,
}
}
}
// Next up we have some implicit files that are loaded automatically
// if they are present. There's the original terraform.tfvars
// (DefaultVarsFilename) along with the later-added search for all files
// ending in .auto.tfvars.
if _, err := os.Stat(DefaultVarsFilename); err == nil {
moreDiags := m.addVarsFromFile(DefaultVarsFilename, terraform.ValueFromAutoFile, ret)
diags = diags.Append(moreDiags)
}
const defaultVarsFilenameJSON = DefaultVarsFilename + ".json"
if _, err := os.Stat(defaultVarsFilenameJSON); err == nil {
moreDiags := m.addVarsFromFile(defaultVarsFilenameJSON, terraform.ValueFromAutoFile, ret)
diags = diags.Append(moreDiags)
}
if infos, err := ioutil.ReadDir("."); err == nil {
// "infos" is already sorted by name, so we just need to filter it here.
for _, info := range infos {
name := info.Name()
if !isAutoVarFile(name) {
continue
}
moreDiags := m.addVarsFromFile(name, terraform.ValueFromAutoFile, ret)
diags = diags.Append(moreDiags)
}
}
// Finally we process values given explicitly on the command line, either
// as individual literal settings or as additional files to read.
for _, rawFlag := range m.variableArgs.AllItems() {
switch rawFlag.Name {
case "-var":
// Value should be in the form "name=value", where value is a
// raw string whose interpretation will depend on the variable's
// parsing mode.
raw := rawFlag.Value
eq := strings.Index(raw, "=")
if eq == -1 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid -var option",
fmt.Sprintf("The given -var option %q is not correctly specified. Must be a variable name and value separated by an equals sign, like -var=\"key=value\".", raw),
))
continue
}
name := raw[:eq]
rawVal := raw[eq+1:]
ret[name] = unparsedVariableValueString{
str: rawVal,
name: name,
sourceType: terraform.ValueFromCLIArg,
}
case "-var-file":
moreDiags := m.addVarsFromFile(rawFlag.Value, terraform.ValueFromNamedFile, ret)
diags = diags.Append(moreDiags)
default:
// Should never happen; always a bug in the code that built up
// the contents of m.variableArgs.
diags = diags.Append(fmt.Errorf("unsupported variable option name %q (this is a bug in Terraform)", rawFlag.Name))
}
}
return ret, diags
}
func (m *Meta) addVarsFromFile(filename string, sourceType terraform.ValueSourceType, to map[string]backend.UnparsedVariableValue) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
src, err := ioutil.ReadFile(filename)
if err != nil {
if os.IsNotExist(err) {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to read variables file",
fmt.Sprintf("Given variables file %s does not exist.", filename),
))
} else {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to read variables file",
fmt.Sprintf("Error while reading %s: %s.", filename, err),
))
}
return diags
}
loader, err := m.initConfigLoader()
if err != nil {
diags = diags.Append(err)
return diags
}
// Record the file source code for snippets in diagnostic messages.
loader.Parser().ForceFileSource(filename, src)
var f *hcl.File
if strings.HasSuffix(filename, ".json") {
var hclDiags hcl.Diagnostics
f, hclDiags = hcljson.Parse(src, filename)
diags = diags.Append(hclDiags)
if f == nil || f.Body == nil {
return diags
}
} else {
var hclDiags hcl.Diagnostics
f, hclDiags = hclsyntax.ParseConfig(src, filename, hcl.Pos{Line: 1, Column: 1})
diags = diags.Append(hclDiags)
if f == nil || f.Body == nil {
return diags
}
}
// Before we do our real decode, we'll probe to see if there are any blocks
// of type "variable" in this body, since it's a common mistake for new
// users to put variable declarations in tfvars rather than variable value
// definitions, and otherwise our error message for that case is not so
// helpful.
{
content, _, _ := f.Body.PartialContent(&hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{
Type: "variable",
LabelNames: []string{"name"},
},
},
})
for _, block := range content.Blocks {
name := block.Labels[0]
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Variable declaration in .tfvars file",
Detail: fmt.Sprintf("A .tfvars file is used to assign values to variables that have already been declared in .tf files, not to declare new variables. To declare variable %q, place this block in one of your .tf files, such as variables.tf.\n\nTo set a value for this variable in %s, use the definition syntax instead:\n %s = <value>", name, block.TypeRange.Filename, name),
Subject: &block.TypeRange,
})
}
if diags.HasErrors() {
// If we already found problems then JustAttributes below will find
// the same problems with less-helpful messages, so we'll bail for
// now to let the user focus on the immediate problem.
return diags
}
}
attrs, hclDiags := f.Body.JustAttributes()
diags = diags.Append(hclDiags)
for name, attr := range attrs {
to[name] = unparsedVariableValueExpression{
expr: attr.Expr,
sourceType: sourceType,
}
}
return diags
}
// unparsedVariableValueLiteral is a backend.UnparsedVariableValue
// implementation that was actually already parsed (!). This is
// intended to deal with expressions inside "tfvars" files.
type unparsedVariableValueExpression struct {
expr hcl.Expression
sourceType terraform.ValueSourceType
}
func (v unparsedVariableValueExpression) ParseVariableValue(mode configs.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
val, hclDiags := v.expr.Value(nil) // nil because no function calls or variable references are allowed here
diags = diags.Append(hclDiags)
rng := tfdiags.SourceRangeFromHCL(v.expr.Range())
return &terraform.InputValue{
Value: val,
SourceType: v.sourceType,
SourceRange: rng,
}, diags
}
// unparsedVariableValueString is a backend.UnparsedVariableValue
// implementation that parses its value from a string. This can be used
// to deal with values given directly on the command line and via environment
// variables.
type unparsedVariableValueString struct {
str string
name string
sourceType terraform.ValueSourceType
}
func (v unparsedVariableValueString) ParseVariableValue(mode configs.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
val, hclDiags := mode.Parse(v.name, v.str)
diags = diags.Append(hclDiags)
return &terraform.InputValue{
Value: val,
SourceType: v.sourceType,
}, diags
}