]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/configs/resource.go
update vendor and go.mod
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / configs / resource.go
1 package configs
2
3 import (
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.
14 type 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.
36 type 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
49 func (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.
55 func (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.
67 func (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
78 func 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 // Cannot have count and for_each on the same resource block
115 if r.Count != nil {
116 diags = append(diags, &hcl.Diagnostic{
117 Severity: hcl.DiagError,
118 Summary: `Invalid combination of "count" and "for_each"`,
119 Detail: `The "count" and "for_each" meta-arguments are mutually-exclusive, only one should be used to be explicit about the number of resources to be created.`,
120 Subject: &attr.NameRange,
121 })
122 }
123 }
124
125 if attr, exists := content.Attributes["provider"]; exists {
126 var providerDiags hcl.Diagnostics
127 r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr.Expr, "provider")
128 diags = append(diags, providerDiags...)
129 }
130
131 if attr, exists := content.Attributes["depends_on"]; exists {
132 deps, depsDiags := decodeDependsOn(attr)
133 diags = append(diags, depsDiags...)
134 r.DependsOn = append(r.DependsOn, deps...)
135 }
136
137 var seenLifecycle *hcl.Block
138 var seenConnection *hcl.Block
139 for _, block := range content.Blocks {
140 switch block.Type {
141 case "lifecycle":
142 if seenLifecycle != nil {
143 diags = append(diags, &hcl.Diagnostic{
144 Severity: hcl.DiagError,
145 Summary: "Duplicate lifecycle block",
146 Detail: fmt.Sprintf("This resource already has a lifecycle block at %s.", seenLifecycle.DefRange),
147 Subject: &block.DefRange,
148 })
149 continue
150 }
151 seenLifecycle = block
152
153 lcContent, lcDiags := block.Body.Content(resourceLifecycleBlockSchema)
154 diags = append(diags, lcDiags...)
155
156 if attr, exists := lcContent.Attributes["create_before_destroy"]; exists {
157 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &r.Managed.CreateBeforeDestroy)
158 diags = append(diags, valDiags...)
159 r.Managed.CreateBeforeDestroySet = true
160 }
161
162 if attr, exists := lcContent.Attributes["prevent_destroy"]; exists {
163 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &r.Managed.PreventDestroy)
164 diags = append(diags, valDiags...)
165 r.Managed.PreventDestroySet = true
166 }
167
168 if attr, exists := lcContent.Attributes["ignore_changes"]; exists {
169
170 // ignore_changes can either be a list of relative traversals
171 // or it can be just the keyword "all" to ignore changes to this
172 // resource entirely.
173 // ignore_changes = [ami, instance_type]
174 // ignore_changes = all
175 // We also allow two legacy forms for compatibility with earlier
176 // versions:
177 // ignore_changes = ["ami", "instance_type"]
178 // ignore_changes = ["*"]
179
180 kw := hcl.ExprAsKeyword(attr.Expr)
181
182 switch {
183 case kw == "all":
184 r.Managed.IgnoreAllChanges = true
185 default:
186 exprs, listDiags := hcl.ExprList(attr.Expr)
187 diags = append(diags, listDiags...)
188
189 var ignoreAllRange hcl.Range
190
191 for _, expr := range exprs {
192
193 // our expr might be the literal string "*", which
194 // we accept as a deprecated way of saying "all".
195 if shimIsIgnoreChangesStar(expr) {
196 r.Managed.IgnoreAllChanges = true
197 ignoreAllRange = expr.Range()
198 diags = append(diags, &hcl.Diagnostic{
199 Severity: hcl.DiagWarning,
200 Summary: "Deprecated ignore_changes wildcard",
201 Detail: "The [\"*\"] form of ignore_changes wildcard is deprecated. Use \"ignore_changes = all\" to ignore changes to all attributes.",
202 Subject: attr.Expr.Range().Ptr(),
203 })
204 continue
205 }
206
207 expr, shimDiags := shimTraversalInString(expr, false)
208 diags = append(diags, shimDiags...)
209
210 traversal, travDiags := hcl.RelTraversalForExpr(expr)
211 diags = append(diags, travDiags...)
212 if len(traversal) != 0 {
213 r.Managed.IgnoreChanges = append(r.Managed.IgnoreChanges, traversal)
214 }
215 }
216
217 if r.Managed.IgnoreAllChanges && len(r.Managed.IgnoreChanges) != 0 {
218 diags = append(diags, &hcl.Diagnostic{
219 Severity: hcl.DiagError,
220 Summary: "Invalid ignore_changes ruleset",
221 Detail: "Cannot mix wildcard string \"*\" with non-wildcard references.",
222 Subject: &ignoreAllRange,
223 Context: attr.Expr.Range().Ptr(),
224 })
225 }
226
227 }
228
229 }
230
231 case "connection":
232 if seenConnection != nil {
233 diags = append(diags, &hcl.Diagnostic{
234 Severity: hcl.DiagError,
235 Summary: "Duplicate connection block",
236 Detail: fmt.Sprintf("This resource already has a connection block at %s.", seenConnection.DefRange),
237 Subject: &block.DefRange,
238 })
239 continue
240 }
241 seenConnection = block
242
243 r.Managed.Connection = &Connection{
244 Config: block.Body,
245 DeclRange: block.DefRange,
246 }
247
248 case "provisioner":
249 pv, pvDiags := decodeProvisionerBlock(block)
250 diags = append(diags, pvDiags...)
251 if pv != nil {
252 r.Managed.Provisioners = append(r.Managed.Provisioners, pv)
253 }
254
255 default:
256 // Any other block types are ones we've reserved for future use,
257 // so they get a generic message.
258 diags = append(diags, &hcl.Diagnostic{
259 Severity: hcl.DiagError,
260 Summary: "Reserved block type name in resource block",
261 Detail: fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
262 Subject: &block.TypeRange,
263 })
264 }
265 }
266
267 return r, diags
268 }
269
270 func decodeDataBlock(block *hcl.Block) (*Resource, hcl.Diagnostics) {
271 r := &Resource{
272 Mode: addrs.DataResourceMode,
273 Type: block.Labels[0],
274 Name: block.Labels[1],
275 DeclRange: block.DefRange,
276 TypeRange: block.LabelRanges[0],
277 }
278
279 content, remain, diags := block.Body.PartialContent(dataBlockSchema)
280 r.Config = remain
281
282 if !hclsyntax.ValidIdentifier(r.Type) {
283 diags = append(diags, &hcl.Diagnostic{
284 Severity: hcl.DiagError,
285 Summary: "Invalid data source name",
286 Detail: badIdentifierDetail,
287 Subject: &block.LabelRanges[0],
288 })
289 }
290 if !hclsyntax.ValidIdentifier(r.Name) {
291 diags = append(diags, &hcl.Diagnostic{
292 Severity: hcl.DiagError,
293 Summary: "Invalid data resource name",
294 Detail: badIdentifierDetail,
295 Subject: &block.LabelRanges[1],
296 })
297 }
298
299 if attr, exists := content.Attributes["count"]; exists {
300 r.Count = attr.Expr
301 }
302
303 if attr, exists := content.Attributes["for_each"]; exists {
304 r.ForEach = attr.Expr
305 // Cannot have count and for_each on the same data block
306 if r.Count != nil {
307 diags = append(diags, &hcl.Diagnostic{
308 Severity: hcl.DiagError,
309 Summary: `Invalid combination of "count" and "for_each"`,
310 Detail: `The "count" and "for_each" meta-arguments are mutually-exclusive, only one should be used to be explicit about the number of resources to be created.`,
311 Subject: &attr.NameRange,
312 })
313 }
314 }
315
316 if attr, exists := content.Attributes["provider"]; exists {
317 var providerDiags hcl.Diagnostics
318 r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr.Expr, "provider")
319 diags = append(diags, providerDiags...)
320 }
321
322 if attr, exists := content.Attributes["depends_on"]; exists {
323 deps, depsDiags := decodeDependsOn(attr)
324 diags = append(diags, depsDiags...)
325 r.DependsOn = append(r.DependsOn, deps...)
326 }
327
328 for _, block := range content.Blocks {
329 // All of the block types we accept are just reserved for future use, but some get a specialized error message.
330 switch block.Type {
331 case "lifecycle":
332 diags = append(diags, &hcl.Diagnostic{
333 Severity: hcl.DiagError,
334 Summary: "Unsupported lifecycle block",
335 Detail: "Data resources do not have lifecycle settings, so a lifecycle block is not allowed.",
336 Subject: &block.DefRange,
337 })
338 default:
339 diags = append(diags, &hcl.Diagnostic{
340 Severity: hcl.DiagError,
341 Summary: "Reserved block type name in data block",
342 Detail: fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
343 Subject: &block.TypeRange,
344 })
345 }
346 }
347
348 return r, diags
349 }
350
351 type ProviderConfigRef struct {
352 Name string
353 NameRange hcl.Range
354 Alias string
355 AliasRange *hcl.Range // nil if alias not set
356 }
357
358 func decodeProviderConfigRef(expr hcl.Expression, argName string) (*ProviderConfigRef, hcl.Diagnostics) {
359 var diags hcl.Diagnostics
360
361 var shimDiags hcl.Diagnostics
362 expr, shimDiags = shimTraversalInString(expr, false)
363 diags = append(diags, shimDiags...)
364
365 traversal, travDiags := hcl.AbsTraversalForExpr(expr)
366
367 // AbsTraversalForExpr produces only generic errors, so we'll discard
368 // the errors given and produce our own with extra context. If we didn't
369 // get any errors then we might still have warnings, though.
370 if !travDiags.HasErrors() {
371 diags = append(diags, travDiags...)
372 }
373
374 if len(traversal) < 1 || len(traversal) > 2 {
375 // A provider reference was given as a string literal in the legacy
376 // configuration language and there are lots of examples out there
377 // showing that usage, so we'll sniff for that situation here and
378 // produce a specialized error message for it to help users find
379 // the new correct form.
380 if exprIsNativeQuotedString(expr) {
381 diags = append(diags, &hcl.Diagnostic{
382 Severity: hcl.DiagError,
383 Summary: "Invalid provider configuration reference",
384 Detail: "A provider configuration reference must not be given in quotes.",
385 Subject: expr.Range().Ptr(),
386 })
387 return nil, diags
388 }
389
390 diags = append(diags, &hcl.Diagnostic{
391 Severity: hcl.DiagError,
392 Summary: "Invalid provider configuration reference",
393 Detail: fmt.Sprintf("The %s argument requires a provider type name, optionally followed by a period and then a configuration alias.", argName),
394 Subject: expr.Range().Ptr(),
395 })
396 return nil, diags
397 }
398
399 ret := &ProviderConfigRef{
400 Name: traversal.RootName(),
401 NameRange: traversal[0].SourceRange(),
402 }
403
404 if len(traversal) > 1 {
405 aliasStep, ok := traversal[1].(hcl.TraverseAttr)
406 if !ok {
407 diags = append(diags, &hcl.Diagnostic{
408 Severity: hcl.DiagError,
409 Summary: "Invalid provider configuration reference",
410 Detail: "Provider name must either stand alone or be followed by a period and then a configuration alias.",
411 Subject: traversal[1].SourceRange().Ptr(),
412 })
413 return ret, diags
414 }
415
416 ret.Alias = aliasStep.Name
417 ret.AliasRange = aliasStep.SourceRange().Ptr()
418 }
419
420 return ret, diags
421 }
422
423 // Addr returns the provider config address corresponding to the receiving
424 // config reference.
425 //
426 // This is a trivial conversion, essentially just discarding the source
427 // location information and keeping just the addressing information.
428 func (r *ProviderConfigRef) Addr() addrs.ProviderConfig {
429 return addrs.ProviderConfig{
430 Type: r.Name,
431 Alias: r.Alias,
432 }
433 }
434
435 func (r *ProviderConfigRef) String() string {
436 if r == nil {
437 return "<nil>"
438 }
439 if r.Alias != "" {
440 return fmt.Sprintf("%s.%s", r.Name, r.Alias)
441 }
442 return r.Name
443 }
444
445 var commonResourceAttributes = []hcl.AttributeSchema{
446 {
447 Name: "count",
448 },
449 {
450 Name: "for_each",
451 },
452 {
453 Name: "provider",
454 },
455 {
456 Name: "depends_on",
457 },
458 }
459
460 var resourceBlockSchema = &hcl.BodySchema{
461 Attributes: commonResourceAttributes,
462 Blocks: []hcl.BlockHeaderSchema{
463 {Type: "locals"}, // reserved for future use
464 {Type: "lifecycle"},
465 {Type: "connection"},
466 {Type: "provisioner", LabelNames: []string{"type"}},
467 },
468 }
469
470 var dataBlockSchema = &hcl.BodySchema{
471 Attributes: commonResourceAttributes,
472 Blocks: []hcl.BlockHeaderSchema{
473 {Type: "lifecycle"}, // reserved for future use
474 {Type: "locals"}, // reserved for future use
475 },
476 }
477
478 var resourceLifecycleBlockSchema = &hcl.BodySchema{
479 Attributes: []hcl.AttributeSchema{
480 {
481 Name: "create_before_destroy",
482 },
483 {
484 Name: "prevent_destroy",
485 },
486 {
487 Name: "ignore_changes",
488 },
489 },
490 }