6 "github.com/hashicorp/hcl2/gohcl"
7 "github.com/hashicorp/hcl2/hcl"
8 "github.com/hashicorp/hcl2/hcl/hclsyntax"
10 "github.com/hashicorp/terraform/addrs"
13 // Resource represents a "resource" or "data" block in a module or file.
14 type Resource struct {
15 Mode addrs.ResourceMode
20 ForEach hcl.Expression
22 ProviderConfigRef *ProviderConfigRef
24 DependsOn []hcl.Traversal
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
35 // ManagedResource represents a "resource" block in a module or file.
36 type ManagedResource struct {
37 Connection *Connection
38 Provisioners []*Provisioner
40 CreateBeforeDestroy bool
42 IgnoreChanges []hcl.Traversal
45 CreateBeforeDestroySet bool
46 PreventDestroySet bool
49 func (r *Resource) moduleUniqueKey() string {
50 return r.Addr().String()
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{
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()
72 return addrs.ProviderConfig{
73 Type: r.ProviderConfigRef.Name,
74 Alias: r.ProviderConfigRef.Alias,
78 func decodeResourceBlock(block *hcl.Block) (*Resource, hcl.Diagnostics) {
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{},
88 content, remain, diags := block.Body.PartialContent(resourceBlockSchema)
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],
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],
108 if attr, exists := content.Attributes["count"]; exists {
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,
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...)
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...)
135 var seenLifecycle *hcl.Block
136 var seenConnection *hcl.Block
137 for _, block := range content.Blocks {
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,
149 seenLifecycle = block
151 lcContent, lcDiags := block.Body.Content(resourceLifecycleBlockSchema)
152 diags = append(diags, lcDiags...)
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
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
166 if attr, exists := lcContent.Attributes["ignore_changes"]; exists {
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
175 // ignore_changes = ["ami", "instance_type"]
176 // ignore_changes = ["*"]
178 kw := hcl.ExprAsKeyword(attr.Expr)
182 r.Managed.IgnoreAllChanges = true
184 exprs, listDiags := hcl.ExprList(attr.Expr)
185 diags = append(diags, listDiags...)
187 var ignoreAllRange hcl.Range
189 for _, expr := range exprs {
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(),
205 expr, shimDiags := shimTraversalInString(expr, false)
206 diags = append(diags, shimDiags...)
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)
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(),
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,
239 seenConnection = block
241 r.Managed.Connection = &Connection{
243 DeclRange: block.DefRange,
247 pv, pvDiags := decodeProvisionerBlock(block)
248 diags = append(diags, pvDiags...)
250 r.Managed.Provisioners = append(r.Managed.Provisioners, pv)
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,
268 func decodeDataBlock(block *hcl.Block) (*Resource, hcl.Diagnostics) {
270 Mode: addrs.DataResourceMode,
271 Type: block.Labels[0],
272 Name: block.Labels[1],
273 DeclRange: block.DefRange,
274 TypeRange: block.LabelRanges[0],
277 content, remain, diags := block.Body.PartialContent(dataBlockSchema)
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],
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],
297 if attr, exists := content.Attributes["count"]; exists {
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,
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...)
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...)
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.
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,
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,
347 type ProviderConfigRef struct {
351 AliasRange *hcl.Range // nil if alias not set
354 func decodeProviderConfigRef(expr hcl.Expression, argName string) (*ProviderConfigRef, hcl.Diagnostics) {
355 var diags hcl.Diagnostics
357 var shimDiags hcl.Diagnostics
358 expr, shimDiags = shimTraversalInString(expr, false)
359 diags = append(diags, shimDiags...)
361 traversal, travDiags := hcl.AbsTraversalForExpr(expr)
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...)
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(),
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(),
395 ret := &ProviderConfigRef{
396 Name: traversal.RootName(),
397 NameRange: traversal[0].SourceRange(),
400 if len(traversal) > 1 {
401 aliasStep, ok := traversal[1].(hcl.TraverseAttr)
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(),
412 ret.Alias = aliasStep.Name
413 ret.AliasRange = aliasStep.SourceRange().Ptr()
419 // Addr returns the provider config address corresponding to the receiving
422 // This is a trivial conversion, essentially just discarding the source
423 // location information and keeping just the addressing information.
424 func (r *ProviderConfigRef) Addr() addrs.ProviderConfig {
425 return addrs.ProviderConfig{
431 func (r *ProviderConfigRef) String() string {
436 return fmt.Sprintf("%s.%s", r.Name, r.Alias)
441 var commonResourceAttributes = []hcl.AttributeSchema{
456 var resourceBlockSchema = &hcl.BodySchema{
457 Attributes: commonResourceAttributes,
458 Blocks: []hcl.BlockHeaderSchema{
459 {Type: "locals"}, // reserved for future use
461 {Type: "connection"},
462 {Type: "provisioner", LabelNames: []string{"type"}},
466 var dataBlockSchema = &hcl.BodySchema{
467 Attributes: commonResourceAttributes,
468 Blocks: []hcl.BlockHeaderSchema{
469 {Type: "lifecycle"}, // reserved for future use
470 {Type: "locals"}, // reserved for future use
474 var resourceLifecycleBlockSchema = &hcl.BodySchema{
475 Attributes: []hcl.AttributeSchema{
477 Name: "create_before_destroy",
480 Name: "prevent_destroy",
483 Name: "ignore_changes",