aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/configs/resource.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/configs/resource.go')
-rw-r--r--vendor/github.com/hashicorp/terraform/configs/resource.go486
1 files changed, 486 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/configs/resource.go b/vendor/github.com/hashicorp/terraform/configs/resource.go
new file mode 100644
index 0000000..de1a343
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/configs/resource.go
@@ -0,0 +1,486 @@
1package configs
2
3import (
4 "fmt"
5
6 "github.com/hashicorp/hcl2/gohcl"
7 "github.com/hashicorp/hcl2/hcl"
8 "github.com/hashicorp/hcl2/hcl/hclsyntax"
9
10 "github.com/hashicorp/terraform/addrs"
11)
12
13// Resource represents a "resource" or "data" block in a module or file.
14type Resource struct {
15 Mode addrs.ResourceMode
16 Name string
17 Type string
18 Config hcl.Body
19 Count hcl.Expression
20 ForEach hcl.Expression
21
22 ProviderConfigRef *ProviderConfigRef
23
24 DependsOn []hcl.Traversal
25
26 // Managed is populated only for Mode = addrs.ManagedResourceMode,
27 // containing the additional fields that apply to managed resources.
28 // For all other resource modes, this field is nil.
29 Managed *ManagedResource
30
31 DeclRange hcl.Range
32 TypeRange hcl.Range
33}
34
35// ManagedResource represents a "resource" block in a module or file.
36type ManagedResource struct {
37 Connection *Connection
38 Provisioners []*Provisioner
39
40 CreateBeforeDestroy bool
41 PreventDestroy bool
42 IgnoreChanges []hcl.Traversal
43 IgnoreAllChanges bool
44
45 CreateBeforeDestroySet bool
46 PreventDestroySet bool
47}
48
49func (r *Resource) moduleUniqueKey() string {
50 return r.Addr().String()
51}
52
53// Addr returns a resource address for the receiver that is relative to the
54// resource's containing module.
55func (r *Resource) Addr() addrs.Resource {
56 return addrs.Resource{
57 Mode: r.Mode,
58 Type: r.Type,
59 Name: r.Name,
60 }
61}
62
63// ProviderConfigAddr returns the address for the provider configuration
64// that should be used for this resource. This function implements the
65// default behavior of extracting the type from the resource type name if
66// an explicit "provider" argument was not provided.
67func (r *Resource) ProviderConfigAddr() addrs.ProviderConfig {
68 if r.ProviderConfigRef == nil {
69 return r.Addr().DefaultProviderConfig()
70 }
71
72 return addrs.ProviderConfig{
73 Type: r.ProviderConfigRef.Name,
74 Alias: r.ProviderConfigRef.Alias,
75 }
76}
77
78func decodeResourceBlock(block *hcl.Block) (*Resource, hcl.Diagnostics) {
79 r := &Resource{
80 Mode: addrs.ManagedResourceMode,
81 Type: block.Labels[0],
82 Name: block.Labels[1],
83 DeclRange: block.DefRange,
84 TypeRange: block.LabelRanges[0],
85 Managed: &ManagedResource{},
86 }
87
88 content, remain, diags := block.Body.PartialContent(resourceBlockSchema)
89 r.Config = remain
90
91 if !hclsyntax.ValidIdentifier(r.Type) {
92 diags = append(diags, &hcl.Diagnostic{
93 Severity: hcl.DiagError,
94 Summary: "Invalid resource type name",
95 Detail: badIdentifierDetail,
96 Subject: &block.LabelRanges[0],
97 })
98 }
99 if !hclsyntax.ValidIdentifier(r.Name) {
100 diags = append(diags, &hcl.Diagnostic{
101 Severity: hcl.DiagError,
102 Summary: "Invalid resource name",
103 Detail: badIdentifierDetail,
104 Subject: &block.LabelRanges[1],
105 })
106 }
107
108 if attr, exists := content.Attributes["count"]; exists {
109 r.Count = attr.Expr
110 }
111
112 if attr, exists := content.Attributes["for_each"]; exists {
113 r.ForEach = attr.Expr
114 // We currently parse this, but don't yet do anything with it.
115 diags = append(diags, &hcl.Diagnostic{
116 Severity: hcl.DiagError,
117 Summary: "Reserved argument name in resource block",
118 Detail: fmt.Sprintf("The name %q is reserved for use in a future version of Terraform.", attr.Name),
119 Subject: &attr.NameRange,
120 })
121 }
122
123 if attr, exists := content.Attributes["provider"]; exists {
124 var providerDiags hcl.Diagnostics
125 r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr.Expr, "provider")
126 diags = append(diags, providerDiags...)
127 }
128
129 if attr, exists := content.Attributes["depends_on"]; exists {
130 deps, depsDiags := decodeDependsOn(attr)
131 diags = append(diags, depsDiags...)
132 r.DependsOn = append(r.DependsOn, deps...)
133 }
134
135 var seenLifecycle *hcl.Block
136 var seenConnection *hcl.Block
137 for _, block := range content.Blocks {
138 switch block.Type {
139 case "lifecycle":
140 if seenLifecycle != nil {
141 diags = append(diags, &hcl.Diagnostic{
142 Severity: hcl.DiagError,
143 Summary: "Duplicate lifecycle block",
144 Detail: fmt.Sprintf("This resource already has a lifecycle block at %s.", seenLifecycle.DefRange),
145 Subject: &block.DefRange,
146 })
147 continue
148 }
149 seenLifecycle = block
150
151 lcContent, lcDiags := block.Body.Content(resourceLifecycleBlockSchema)
152 diags = append(diags, lcDiags...)
153
154 if attr, exists := lcContent.Attributes["create_before_destroy"]; exists {
155 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &r.Managed.CreateBeforeDestroy)
156 diags = append(diags, valDiags...)
157 r.Managed.CreateBeforeDestroySet = true
158 }
159
160 if attr, exists := lcContent.Attributes["prevent_destroy"]; exists {
161 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &r.Managed.PreventDestroy)
162 diags = append(diags, valDiags...)
163 r.Managed.PreventDestroySet = true
164 }
165
166 if attr, exists := lcContent.Attributes["ignore_changes"]; exists {
167
168 // ignore_changes can either be a list of relative traversals
169 // or it can be just the keyword "all" to ignore changes to this
170 // resource entirely.
171 // ignore_changes = [ami, instance_type]
172 // ignore_changes = all
173 // We also allow two legacy forms for compatibility with earlier
174 // versions:
175 // ignore_changes = ["ami", "instance_type"]
176 // ignore_changes = ["*"]
177
178 kw := hcl.ExprAsKeyword(attr.Expr)
179
180 switch {
181 case kw == "all":
182 r.Managed.IgnoreAllChanges = true
183 default:
184 exprs, listDiags := hcl.ExprList(attr.Expr)
185 diags = append(diags, listDiags...)
186
187 var ignoreAllRange hcl.Range
188
189 for _, expr := range exprs {
190
191 // our expr might be the literal string "*", which
192 // we accept as a deprecated way of saying "all".
193 if shimIsIgnoreChangesStar(expr) {
194 r.Managed.IgnoreAllChanges = true
195 ignoreAllRange = expr.Range()
196 diags = append(diags, &hcl.Diagnostic{
197 Severity: hcl.DiagWarning,
198 Summary: "Deprecated ignore_changes wildcard",
199 Detail: "The [\"*\"] form of ignore_changes wildcard is deprecated. Use \"ignore_changes = all\" to ignore changes to all attributes.",
200 Subject: attr.Expr.Range().Ptr(),
201 })
202 continue
203 }
204
205 expr, shimDiags := shimTraversalInString(expr, false)
206 diags = append(diags, shimDiags...)
207
208 traversal, travDiags := hcl.RelTraversalForExpr(expr)
209 diags = append(diags, travDiags...)
210 if len(traversal) != 0 {
211 r.Managed.IgnoreChanges = append(r.Managed.IgnoreChanges, traversal)
212 }
213 }
214
215 if r.Managed.IgnoreAllChanges && len(r.Managed.IgnoreChanges) != 0 {
216 diags = append(diags, &hcl.Diagnostic{
217 Severity: hcl.DiagError,
218 Summary: "Invalid ignore_changes ruleset",
219 Detail: "Cannot mix wildcard string \"*\" with non-wildcard references.",
220 Subject: &ignoreAllRange,
221 Context: attr.Expr.Range().Ptr(),
222 })
223 }
224
225 }
226
227 }
228
229 case "connection":
230 if seenConnection != nil {
231 diags = append(diags, &hcl.Diagnostic{
232 Severity: hcl.DiagError,
233 Summary: "Duplicate connection block",
234 Detail: fmt.Sprintf("This resource already has a connection block at %s.", seenConnection.DefRange),
235 Subject: &block.DefRange,
236 })
237 continue
238 }
239 seenConnection = block
240
241 r.Managed.Connection = &Connection{
242 Config: block.Body,
243 DeclRange: block.DefRange,
244 }
245
246 case "provisioner":
247 pv, pvDiags := decodeProvisionerBlock(block)
248 diags = append(diags, pvDiags...)
249 if pv != nil {
250 r.Managed.Provisioners = append(r.Managed.Provisioners, pv)
251 }
252
253 default:
254 // Any other block types are ones we've reserved for future use,
255 // so they get a generic message.
256 diags = append(diags, &hcl.Diagnostic{
257 Severity: hcl.DiagError,
258 Summary: "Reserved block type name in resource block",
259 Detail: fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
260 Subject: &block.TypeRange,
261 })
262 }
263 }
264
265 return r, diags
266}
267
268func decodeDataBlock(block *hcl.Block) (*Resource, hcl.Diagnostics) {
269 r := &Resource{
270 Mode: addrs.DataResourceMode,
271 Type: block.Labels[0],
272 Name: block.Labels[1],
273 DeclRange: block.DefRange,
274 TypeRange: block.LabelRanges[0],
275 }
276
277 content, remain, diags := block.Body.PartialContent(dataBlockSchema)
278 r.Config = remain
279
280 if !hclsyntax.ValidIdentifier(r.Type) {
281 diags = append(diags, &hcl.Diagnostic{
282 Severity: hcl.DiagError,
283 Summary: "Invalid data source name",
284 Detail: badIdentifierDetail,
285 Subject: &block.LabelRanges[0],
286 })
287 }
288 if !hclsyntax.ValidIdentifier(r.Name) {
289 diags = append(diags, &hcl.Diagnostic{
290 Severity: hcl.DiagError,
291 Summary: "Invalid data resource name",
292 Detail: badIdentifierDetail,
293 Subject: &block.LabelRanges[1],
294 })
295 }
296
297 if attr, exists := content.Attributes["count"]; exists {
298 r.Count = attr.Expr
299 }
300
301 if attr, exists := content.Attributes["for_each"]; exists {
302 r.ForEach = attr.Expr
303 // We currently parse this, but don't yet do anything with it.
304 diags = append(diags, &hcl.Diagnostic{
305 Severity: hcl.DiagError,
306 Summary: "Reserved argument name in module block",
307 Detail: fmt.Sprintf("The name %q is reserved for use in a future version of Terraform.", attr.Name),
308 Subject: &attr.NameRange,
309 })
310 }
311
312 if attr, exists := content.Attributes["provider"]; exists {
313 var providerDiags hcl.Diagnostics
314 r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr.Expr, "provider")
315 diags = append(diags, providerDiags...)
316 }
317
318 if attr, exists := content.Attributes["depends_on"]; exists {
319 deps, depsDiags := decodeDependsOn(attr)
320 diags = append(diags, depsDiags...)
321 r.DependsOn = append(r.DependsOn, deps...)
322 }
323
324 for _, block := range content.Blocks {
325 // All of the block types we accept are just reserved for future use, but some get a specialized error message.
326 switch block.Type {
327 case "lifecycle":
328 diags = append(diags, &hcl.Diagnostic{
329 Severity: hcl.DiagError,
330 Summary: "Unsupported lifecycle block",
331 Detail: "Data resources do not have lifecycle settings, so a lifecycle block is not allowed.",
332 Subject: &block.DefRange,
333 })
334 default:
335 diags = append(diags, &hcl.Diagnostic{
336 Severity: hcl.DiagError,
337 Summary: "Reserved block type name in data block",
338 Detail: fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
339 Subject: &block.TypeRange,
340 })
341 }
342 }
343
344 return r, diags
345}
346
347type ProviderConfigRef struct {
348 Name string
349 NameRange hcl.Range
350 Alias string
351 AliasRange *hcl.Range // nil if alias not set
352}
353
354func decodeProviderConfigRef(expr hcl.Expression, argName string) (*ProviderConfigRef, hcl.Diagnostics) {
355 var diags hcl.Diagnostics
356
357 var shimDiags hcl.Diagnostics
358 expr, shimDiags = shimTraversalInString(expr, false)
359 diags = append(diags, shimDiags...)
360
361 traversal, travDiags := hcl.AbsTraversalForExpr(expr)
362
363 // AbsTraversalForExpr produces only generic errors, so we'll discard
364 // the errors given and produce our own with extra context. If we didn't
365 // get any errors then we might still have warnings, though.
366 if !travDiags.HasErrors() {
367 diags = append(diags, travDiags...)
368 }
369
370 if len(traversal) < 1 || len(traversal) > 2 {
371 // A provider reference was given as a string literal in the legacy
372 // configuration language and there are lots of examples out there
373 // showing that usage, so we'll sniff for that situation here and
374 // produce a specialized error message for it to help users find
375 // the new correct form.
376 if exprIsNativeQuotedString(expr) {
377 diags = append(diags, &hcl.Diagnostic{
378 Severity: hcl.DiagError,
379 Summary: "Invalid provider configuration reference",
380 Detail: "A provider configuration reference must not be given in quotes.",
381 Subject: expr.Range().Ptr(),
382 })
383 return nil, diags
384 }
385
386 diags = append(diags, &hcl.Diagnostic{
387 Severity: hcl.DiagError,
388 Summary: "Invalid provider configuration reference",
389 Detail: fmt.Sprintf("The %s argument requires a provider type name, optionally followed by a period and then a configuration alias.", argName),
390 Subject: expr.Range().Ptr(),
391 })
392 return nil, diags
393 }
394
395 ret := &ProviderConfigRef{
396 Name: traversal.RootName(),
397 NameRange: traversal[0].SourceRange(),
398 }
399
400 if len(traversal) > 1 {
401 aliasStep, ok := traversal[1].(hcl.TraverseAttr)
402 if !ok {
403 diags = append(diags, &hcl.Diagnostic{
404 Severity: hcl.DiagError,
405 Summary: "Invalid provider configuration reference",
406 Detail: "Provider name must either stand alone or be followed by a period and then a configuration alias.",
407 Subject: traversal[1].SourceRange().Ptr(),
408 })
409 return ret, diags
410 }
411
412 ret.Alias = aliasStep.Name
413 ret.AliasRange = aliasStep.SourceRange().Ptr()
414 }
415
416 return ret, diags
417}
418
419// Addr returns the provider config address corresponding to the receiving
420// config reference.
421//
422// This is a trivial conversion, essentially just discarding the source
423// location information and keeping just the addressing information.
424func (r *ProviderConfigRef) Addr() addrs.ProviderConfig {
425 return addrs.ProviderConfig{
426 Type: r.Name,
427 Alias: r.Alias,
428 }
429}
430
431func (r *ProviderConfigRef) String() string {
432 if r == nil {
433 return "<nil>"
434 }
435 if r.Alias != "" {
436 return fmt.Sprintf("%s.%s", r.Name, r.Alias)
437 }
438 return r.Name
439}
440
441var commonResourceAttributes = []hcl.AttributeSchema{
442 {
443 Name: "count",
444 },
445 {
446 Name: "for_each",
447 },
448 {
449 Name: "provider",
450 },
451 {
452 Name: "depends_on",
453 },
454}
455
456var resourceBlockSchema = &hcl.BodySchema{
457 Attributes: commonResourceAttributes,
458 Blocks: []hcl.BlockHeaderSchema{
459 {Type: "locals"}, // reserved for future use
460 {Type: "lifecycle"},
461 {Type: "connection"},
462 {Type: "provisioner", LabelNames: []string{"type"}},
463 },
464}
465
466var dataBlockSchema = &hcl.BodySchema{
467 Attributes: commonResourceAttributes,
468 Blocks: []hcl.BlockHeaderSchema{
469 {Type: "lifecycle"}, // reserved for future use
470 {Type: "locals"}, // reserved for future use
471 },
472}
473
474var resourceLifecycleBlockSchema = &hcl.BodySchema{
475 Attributes: []hcl.AttributeSchema{
476 {
477 Name: "create_before_destroy",
478 },
479 {
480 Name: "prevent_destroy",
481 },
482 {
483 Name: "ignore_changes",
484 },
485 },
486}