]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/configs/named_values.go
Merge pull request #32 from ndench/0.12-compatibility
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / configs / named_values.go
CommitLineData
107c1cdb
ND
1package configs
2
3import (
4 "fmt"
5
6 "github.com/hashicorp/hcl2/ext/typeexpr"
7 "github.com/hashicorp/hcl2/gohcl"
8 "github.com/hashicorp/hcl2/hcl"
9 "github.com/hashicorp/hcl2/hcl/hclsyntax"
10 "github.com/zclconf/go-cty/cty"
11 "github.com/zclconf/go-cty/cty/convert"
12
13 "github.com/hashicorp/terraform/addrs"
14)
15
16// A consistent detail message for all "not a valid identifier" diagnostics.
17const badIdentifierDetail = "A name must start with a letter and may contain only letters, digits, underscores, and dashes."
18
19// Variable represents a "variable" block in a module or file.
20type Variable struct {
21 Name string
22 Description string
23 Default cty.Value
24 Type cty.Type
25 ParsingMode VariableParsingMode
26
27 DescriptionSet bool
28
29 DeclRange hcl.Range
30}
31
32func decodeVariableBlock(block *hcl.Block, override bool) (*Variable, hcl.Diagnostics) {
33 v := &Variable{
34 Name: block.Labels[0],
35 DeclRange: block.DefRange,
36 }
37
38 // Unless we're building an override, we'll set some defaults
39 // which we might override with attributes below. We leave these
40 // as zero-value in the override case so we can recognize whether
41 // or not they are set when we merge.
42 if !override {
43 v.Type = cty.DynamicPseudoType
44 v.ParsingMode = VariableParseLiteral
45 }
46
47 content, diags := block.Body.Content(variableBlockSchema)
48
49 if !hclsyntax.ValidIdentifier(v.Name) {
50 diags = append(diags, &hcl.Diagnostic{
51 Severity: hcl.DiagError,
52 Summary: "Invalid variable name",
53 Detail: badIdentifierDetail,
54 Subject: &block.LabelRanges[0],
55 })
56 }
57
58 // Don't allow declaration of variables that would conflict with the
59 // reserved attribute and block type names in a "module" block, since
60 // these won't be usable for child modules.
61 for _, attr := range moduleBlockSchema.Attributes {
62 if attr.Name == v.Name {
63 diags = append(diags, &hcl.Diagnostic{
64 Severity: hcl.DiagError,
65 Summary: "Invalid variable name",
66 Detail: fmt.Sprintf("The variable name %q is reserved due to its special meaning inside module blocks.", attr.Name),
67 Subject: &block.LabelRanges[0],
68 })
69 }
70 }
71 for _, blockS := range moduleBlockSchema.Blocks {
72 if blockS.Type == v.Name {
73 diags = append(diags, &hcl.Diagnostic{
74 Severity: hcl.DiagError,
75 Summary: "Invalid variable name",
76 Detail: fmt.Sprintf("The variable name %q is reserved due to its special meaning inside module blocks.", blockS.Type),
77 Subject: &block.LabelRanges[0],
78 })
79 }
80 }
81
82 if attr, exists := content.Attributes["description"]; exists {
83 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Description)
84 diags = append(diags, valDiags...)
85 v.DescriptionSet = true
86 }
87
88 if attr, exists := content.Attributes["type"]; exists {
89 ty, parseMode, tyDiags := decodeVariableType(attr.Expr)
90 diags = append(diags, tyDiags...)
91 v.Type = ty
92 v.ParsingMode = parseMode
93 }
94
95 if attr, exists := content.Attributes["default"]; exists {
96 val, valDiags := attr.Expr.Value(nil)
97 diags = append(diags, valDiags...)
98
99 // Convert the default to the expected type so we can catch invalid
100 // defaults early and allow later code to assume validity.
101 // Note that this depends on us having already processed any "type"
102 // attribute above.
103 // However, we can't do this if we're in an override file where
104 // the type might not be set; we'll catch that during merge.
105 if v.Type != cty.NilType {
106 var err error
107 val, err = convert.Convert(val, v.Type)
108 if err != nil {
109 diags = append(diags, &hcl.Diagnostic{
110 Severity: hcl.DiagError,
111 Summary: "Invalid default value for variable",
112 Detail: fmt.Sprintf("This default value is not compatible with the variable's type constraint: %s.", err),
113 Subject: attr.Expr.Range().Ptr(),
114 })
115 val = cty.DynamicVal
116 }
117 }
118
119 v.Default = val
120 }
121
122 return v, diags
123}
124
125func decodeVariableType(expr hcl.Expression) (cty.Type, VariableParsingMode, hcl.Diagnostics) {
126 if exprIsNativeQuotedString(expr) {
127 // Here we're accepting the pre-0.12 form of variable type argument where
128 // the string values "string", "list" and "map" are accepted has a hint
129 // about the type used primarily for deciding how to parse values
130 // given on the command line and in environment variables.
131 // Only the native syntax ends up in this codepath; we handle the
132 // JSON syntax (which is, of course, quoted even in the new format)
133 // in the normal codepath below.
134 val, diags := expr.Value(nil)
135 if diags.HasErrors() {
136 return cty.DynamicPseudoType, VariableParseHCL, diags
137 }
138 str := val.AsString()
139 switch str {
140 case "string":
141 return cty.String, VariableParseLiteral, diags
142 case "list":
143 return cty.List(cty.DynamicPseudoType), VariableParseHCL, diags
144 case "map":
145 return cty.Map(cty.DynamicPseudoType), VariableParseHCL, diags
146 default:
147 return cty.DynamicPseudoType, VariableParseHCL, hcl.Diagnostics{{
148 Severity: hcl.DiagError,
149 Summary: "Invalid legacy variable type hint",
150 Detail: `The legacy variable type hint form, using a quoted string, allows only the values "string", "list", and "map". To provide a full type expression, remove the surrounding quotes and give the type expression directly.`,
151 Subject: expr.Range().Ptr(),
152 }}
153 }
154 }
155
156 // First we'll deal with some shorthand forms that the HCL-level type
157 // expression parser doesn't include. These both emulate pre-0.12 behavior
158 // of allowing a list or map of any element type as long as all of the
159 // elements are consistent. This is the same as list(any) or map(any).
160 switch hcl.ExprAsKeyword(expr) {
161 case "list":
162 return cty.List(cty.DynamicPseudoType), VariableParseHCL, nil
163 case "map":
164 return cty.Map(cty.DynamicPseudoType), VariableParseHCL, nil
165 }
166
167 ty, diags := typeexpr.TypeConstraint(expr)
168 if diags.HasErrors() {
169 return cty.DynamicPseudoType, VariableParseHCL, diags
170 }
171
172 switch {
173 case ty.IsPrimitiveType():
174 // Primitive types use literal parsing.
175 return ty, VariableParseLiteral, diags
176 default:
177 // Everything else uses HCL parsing
178 return ty, VariableParseHCL, diags
179 }
180}
181
182// VariableParsingMode defines how values of a particular variable given by
183// text-only mechanisms (command line arguments and environment variables)
184// should be parsed to produce the final value.
185type VariableParsingMode rune
186
187// VariableParseLiteral is a variable parsing mode that just takes the given
188// string directly as a cty.String value.
189const VariableParseLiteral VariableParsingMode = 'L'
190
191// VariableParseHCL is a variable parsing mode that attempts to parse the given
192// string as an HCL expression and returns the result.
193const VariableParseHCL VariableParsingMode = 'H'
194
195// Parse uses the receiving parsing mode to process the given variable value
196// string, returning the result along with any diagnostics.
197//
198// A VariableParsingMode does not know the expected type of the corresponding
199// variable, so it's the caller's responsibility to attempt to convert the
200// result to the appropriate type and return to the user any diagnostics that
201// conversion may produce.
202//
203// The given name is used to create a synthetic filename in case any diagnostics
204// must be generated about the given string value. This should be the name
205// of the root module variable whose value will be populated from the given
206// string.
207//
208// If the returned diagnostics has errors, the returned value may not be
209// valid.
210func (m VariableParsingMode) Parse(name, value string) (cty.Value, hcl.Diagnostics) {
211 switch m {
212 case VariableParseLiteral:
213 return cty.StringVal(value), nil
214 case VariableParseHCL:
215 fakeFilename := fmt.Sprintf("<value for var.%s>", name)
216 expr, diags := hclsyntax.ParseExpression([]byte(value), fakeFilename, hcl.Pos{Line: 1, Column: 1})
217 if diags.HasErrors() {
218 return cty.DynamicVal, diags
219 }
220 val, valDiags := expr.Value(nil)
221 diags = append(diags, valDiags...)
222 return val, diags
223 default:
224 // Should never happen
225 panic(fmt.Errorf("Parse called on invalid VariableParsingMode %#v", m))
226 }
227}
228
229// Output represents an "output" block in a module or file.
230type Output struct {
231 Name string
232 Description string
233 Expr hcl.Expression
234 DependsOn []hcl.Traversal
235 Sensitive bool
236
237 DescriptionSet bool
238 SensitiveSet bool
239
240 DeclRange hcl.Range
241}
242
243func decodeOutputBlock(block *hcl.Block, override bool) (*Output, hcl.Diagnostics) {
244 o := &Output{
245 Name: block.Labels[0],
246 DeclRange: block.DefRange,
247 }
248
249 schema := outputBlockSchema
250 if override {
251 schema = schemaForOverrides(schema)
252 }
253
254 content, diags := block.Body.Content(schema)
255
256 if !hclsyntax.ValidIdentifier(o.Name) {
257 diags = append(diags, &hcl.Diagnostic{
258 Severity: hcl.DiagError,
259 Summary: "Invalid output name",
260 Detail: badIdentifierDetail,
261 Subject: &block.LabelRanges[0],
262 })
263 }
264
265 if attr, exists := content.Attributes["description"]; exists {
266 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &o.Description)
267 diags = append(diags, valDiags...)
268 o.DescriptionSet = true
269 }
270
271 if attr, exists := content.Attributes["value"]; exists {
272 o.Expr = attr.Expr
273 }
274
275 if attr, exists := content.Attributes["sensitive"]; exists {
276 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &o.Sensitive)
277 diags = append(diags, valDiags...)
278 o.SensitiveSet = true
279 }
280
281 if attr, exists := content.Attributes["depends_on"]; exists {
282 deps, depsDiags := decodeDependsOn(attr)
283 diags = append(diags, depsDiags...)
284 o.DependsOn = append(o.DependsOn, deps...)
285 }
286
287 return o, diags
288}
289
290// Local represents a single entry from a "locals" block in a module or file.
291// The "locals" block itself is not represented, because it serves only to
292// provide context for us to interpret its contents.
293type Local struct {
294 Name string
295 Expr hcl.Expression
296
297 DeclRange hcl.Range
298}
299
300func decodeLocalsBlock(block *hcl.Block) ([]*Local, hcl.Diagnostics) {
301 attrs, diags := block.Body.JustAttributes()
302 if len(attrs) == 0 {
303 return nil, diags
304 }
305
306 locals := make([]*Local, 0, len(attrs))
307 for name, attr := range attrs {
308 if !hclsyntax.ValidIdentifier(name) {
309 diags = append(diags, &hcl.Diagnostic{
310 Severity: hcl.DiagError,
311 Summary: "Invalid local value name",
312 Detail: badIdentifierDetail,
313 Subject: &attr.NameRange,
314 })
315 }
316
317 locals = append(locals, &Local{
318 Name: name,
319 Expr: attr.Expr,
320 DeclRange: attr.Range,
321 })
322 }
323 return locals, diags
324}
325
326// Addr returns the address of the local value declared by the receiver,
327// relative to its containing module.
328func (l *Local) Addr() addrs.LocalValue {
329 return addrs.LocalValue{
330 Name: l.Name,
331 }
332}
333
334var variableBlockSchema = &hcl.BodySchema{
335 Attributes: []hcl.AttributeSchema{
336 {
337 Name: "description",
338 },
339 {
340 Name: "default",
341 },
342 {
343 Name: "type",
344 },
345 },
346}
347
348var outputBlockSchema = &hcl.BodySchema{
349 Attributes: []hcl.AttributeSchema{
350 {
351 Name: "description",
352 },
353 {
354 Name: "value",
355 Required: true,
356 },
357 {
358 Name: "depends_on",
359 },
360 {
361 Name: "sensitive",
362 },
363 },
364}