github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/configs/resource.go (about) 1 package configs 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl/v2" 7 "github.com/hashicorp/hcl/v2/gohcl" 8 "github.com/hashicorp/hcl/v2/hclsyntax" 9 hcljson "github.com/hashicorp/hcl/v2/json" 10 11 "github.com/eliastor/durgaform/internal/addrs" 12 "github.com/eliastor/durgaform/internal/lang" 13 "github.com/eliastor/durgaform/internal/tfdiags" 14 ) 15 16 // Resource represents a "resource" or "data" block in a module or file. 17 type Resource struct { 18 Mode addrs.ResourceMode 19 Name string 20 Type string 21 Config hcl.Body 22 Count hcl.Expression 23 ForEach hcl.Expression 24 25 ProviderConfigRef *ProviderConfigRef 26 Provider addrs.Provider 27 28 Preconditions []*CheckRule 29 Postconditions []*CheckRule 30 31 DependsOn []hcl.Traversal 32 33 TriggersReplacement []hcl.Expression 34 35 // Managed is populated only for Mode = addrs.ManagedResourceMode, 36 // containing the additional fields that apply to managed resources. 37 // For all other resource modes, this field is nil. 38 Managed *ManagedResource 39 40 DeclRange hcl.Range 41 TypeRange hcl.Range 42 } 43 44 // ManagedResource represents a "resource" block in a module or file. 45 type ManagedResource struct { 46 Connection *Connection 47 Provisioners []*Provisioner 48 49 CreateBeforeDestroy bool 50 PreventDestroy bool 51 IgnoreChanges []hcl.Traversal 52 IgnoreAllChanges bool 53 54 CreateBeforeDestroySet bool 55 PreventDestroySet bool 56 } 57 58 func (r *Resource) moduleUniqueKey() string { 59 return r.Addr().String() 60 } 61 62 // Addr returns a resource address for the receiver that is relative to the 63 // resource's containing module. 64 func (r *Resource) Addr() addrs.Resource { 65 return addrs.Resource{ 66 Mode: r.Mode, 67 Type: r.Type, 68 Name: r.Name, 69 } 70 } 71 72 // ProviderConfigAddr returns the address for the provider configuration that 73 // should be used for this resource. This function returns a default provider 74 // config addr if an explicit "provider" argument was not provided. 75 func (r *Resource) ProviderConfigAddr() addrs.LocalProviderConfig { 76 if r.ProviderConfigRef == nil { 77 // If no specific "provider" argument is given, we want to look up the 78 // provider config where the local name matches the implied provider 79 // from the resource type. This may be different from the resource's 80 // provider type. 81 return addrs.LocalProviderConfig{ 82 LocalName: r.Addr().ImpliedProvider(), 83 } 84 } 85 86 return addrs.LocalProviderConfig{ 87 LocalName: r.ProviderConfigRef.Name, 88 Alias: r.ProviderConfigRef.Alias, 89 } 90 } 91 92 // HasCustomConditions returns true if and only if the resource has at least 93 // one author-specified custom condition. 94 func (r *Resource) HasCustomConditions() bool { 95 return len(r.Postconditions) != 0 || len(r.Preconditions) != 0 96 } 97 98 func decodeResourceBlock(block *hcl.Block, override bool) (*Resource, hcl.Diagnostics) { 99 var diags hcl.Diagnostics 100 r := &Resource{ 101 Mode: addrs.ManagedResourceMode, 102 Type: block.Labels[0], 103 Name: block.Labels[1], 104 DeclRange: block.DefRange, 105 TypeRange: block.LabelRanges[0], 106 Managed: &ManagedResource{}, 107 } 108 109 content, remain, moreDiags := block.Body.PartialContent(resourceBlockSchema) 110 diags = append(diags, moreDiags...) 111 r.Config = remain 112 113 if !hclsyntax.ValidIdentifier(r.Type) { 114 diags = append(diags, &hcl.Diagnostic{ 115 Severity: hcl.DiagError, 116 Summary: "Invalid resource type name", 117 Detail: badIdentifierDetail, 118 Subject: &block.LabelRanges[0], 119 }) 120 } 121 if !hclsyntax.ValidIdentifier(r.Name) { 122 diags = append(diags, &hcl.Diagnostic{ 123 Severity: hcl.DiagError, 124 Summary: "Invalid resource name", 125 Detail: badIdentifierDetail, 126 Subject: &block.LabelRanges[1], 127 }) 128 } 129 130 if attr, exists := content.Attributes["count"]; exists { 131 r.Count = attr.Expr 132 } 133 134 if attr, exists := content.Attributes["for_each"]; exists { 135 r.ForEach = attr.Expr 136 // Cannot have count and for_each on the same resource block 137 if r.Count != nil { 138 diags = append(diags, &hcl.Diagnostic{ 139 Severity: hcl.DiagError, 140 Summary: `Invalid combination of "count" and "for_each"`, 141 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.`, 142 Subject: &attr.NameRange, 143 }) 144 } 145 } 146 147 if attr, exists := content.Attributes["provider"]; exists { 148 var providerDiags hcl.Diagnostics 149 r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr.Expr, "provider") 150 diags = append(diags, providerDiags...) 151 } 152 153 if attr, exists := content.Attributes["depends_on"]; exists { 154 deps, depsDiags := decodeDependsOn(attr) 155 diags = append(diags, depsDiags...) 156 r.DependsOn = append(r.DependsOn, deps...) 157 } 158 159 var seenLifecycle *hcl.Block 160 var seenConnection *hcl.Block 161 var seenEscapeBlock *hcl.Block 162 for _, block := range content.Blocks { 163 switch block.Type { 164 case "lifecycle": 165 if seenLifecycle != nil { 166 diags = append(diags, &hcl.Diagnostic{ 167 Severity: hcl.DiagError, 168 Summary: "Duplicate lifecycle block", 169 Detail: fmt.Sprintf("This resource already has a lifecycle block at %s.", seenLifecycle.DefRange), 170 Subject: &block.DefRange, 171 }) 172 continue 173 } 174 seenLifecycle = block 175 176 lcContent, lcDiags := block.Body.Content(resourceLifecycleBlockSchema) 177 diags = append(diags, lcDiags...) 178 179 if attr, exists := lcContent.Attributes["create_before_destroy"]; exists { 180 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &r.Managed.CreateBeforeDestroy) 181 diags = append(diags, valDiags...) 182 r.Managed.CreateBeforeDestroySet = true 183 } 184 185 if attr, exists := lcContent.Attributes["prevent_destroy"]; exists { 186 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &r.Managed.PreventDestroy) 187 diags = append(diags, valDiags...) 188 r.Managed.PreventDestroySet = true 189 } 190 191 if attr, exists := lcContent.Attributes["replace_triggered_by"]; exists { 192 exprs, hclDiags := decodeReplaceTriggeredBy(attr.Expr) 193 diags = diags.Extend(hclDiags) 194 195 r.TriggersReplacement = append(r.TriggersReplacement, exprs...) 196 } 197 198 if attr, exists := lcContent.Attributes["ignore_changes"]; exists { 199 200 // ignore_changes can either be a list of relative traversals 201 // or it can be just the keyword "all" to ignore changes to this 202 // resource entirely. 203 // ignore_changes = [ami, instance_type] 204 // ignore_changes = all 205 // We also allow two legacy forms for compatibility with earlier 206 // versions: 207 // ignore_changes = ["ami", "instance_type"] 208 // ignore_changes = ["*"] 209 210 kw := hcl.ExprAsKeyword(attr.Expr) 211 212 switch { 213 case kw == "all": 214 r.Managed.IgnoreAllChanges = true 215 default: 216 exprs, listDiags := hcl.ExprList(attr.Expr) 217 diags = append(diags, listDiags...) 218 219 var ignoreAllRange hcl.Range 220 221 for _, expr := range exprs { 222 223 // our expr might be the literal string "*", which 224 // we accept as a deprecated way of saying "all". 225 if shimIsIgnoreChangesStar(expr) { 226 r.Managed.IgnoreAllChanges = true 227 ignoreAllRange = expr.Range() 228 diags = append(diags, &hcl.Diagnostic{ 229 Severity: hcl.DiagError, 230 Summary: "Invalid ignore_changes wildcard", 231 Detail: "The [\"*\"] form of ignore_changes wildcard is was deprecated and is now invalid. Use \"ignore_changes = all\" to ignore changes to all attributes.", 232 Subject: attr.Expr.Range().Ptr(), 233 }) 234 continue 235 } 236 237 expr, shimDiags := shimTraversalInString(expr, false) 238 diags = append(diags, shimDiags...) 239 240 traversal, travDiags := hcl.RelTraversalForExpr(expr) 241 diags = append(diags, travDiags...) 242 if len(traversal) != 0 { 243 r.Managed.IgnoreChanges = append(r.Managed.IgnoreChanges, traversal) 244 } 245 } 246 247 if r.Managed.IgnoreAllChanges && len(r.Managed.IgnoreChanges) != 0 { 248 diags = append(diags, &hcl.Diagnostic{ 249 Severity: hcl.DiagError, 250 Summary: "Invalid ignore_changes ruleset", 251 Detail: "Cannot mix wildcard string \"*\" with non-wildcard references.", 252 Subject: &ignoreAllRange, 253 Context: attr.Expr.Range().Ptr(), 254 }) 255 } 256 257 } 258 } 259 260 for _, block := range lcContent.Blocks { 261 switch block.Type { 262 case "precondition", "postcondition": 263 cr, moreDiags := decodeCheckRuleBlock(block, override) 264 diags = append(diags, moreDiags...) 265 266 moreDiags = cr.validateSelfReferences(block.Type, r.Addr()) 267 diags = append(diags, moreDiags...) 268 269 switch block.Type { 270 case "precondition": 271 r.Preconditions = append(r.Preconditions, cr) 272 case "postcondition": 273 r.Postconditions = append(r.Postconditions, cr) 274 } 275 default: 276 // The cases above should be exhaustive for all block types 277 // defined in the lifecycle schema, so this shouldn't happen. 278 panic(fmt.Sprintf("unexpected lifecycle sub-block type %q", block.Type)) 279 } 280 } 281 282 case "connection": 283 if seenConnection != nil { 284 diags = append(diags, &hcl.Diagnostic{ 285 Severity: hcl.DiagError, 286 Summary: "Duplicate connection block", 287 Detail: fmt.Sprintf("This resource already has a connection block at %s.", seenConnection.DefRange), 288 Subject: &block.DefRange, 289 }) 290 continue 291 } 292 seenConnection = block 293 294 r.Managed.Connection = &Connection{ 295 Config: block.Body, 296 DeclRange: block.DefRange, 297 } 298 299 case "provisioner": 300 pv, pvDiags := decodeProvisionerBlock(block) 301 diags = append(diags, pvDiags...) 302 if pv != nil { 303 r.Managed.Provisioners = append(r.Managed.Provisioners, pv) 304 } 305 306 case "_": 307 if seenEscapeBlock != nil { 308 diags = append(diags, &hcl.Diagnostic{ 309 Severity: hcl.DiagError, 310 Summary: "Duplicate escaping block", 311 Detail: fmt.Sprintf( 312 "The special block type \"_\" can be used to force particular arguments to be interpreted as resource-type-specific rather than as meta-arguments, but each resource block can have only one such block. The first escaping block was at %s.", 313 seenEscapeBlock.DefRange, 314 ), 315 Subject: &block.DefRange, 316 }) 317 continue 318 } 319 seenEscapeBlock = block 320 321 // When there's an escaping block its content merges with the 322 // existing config we extracted earlier, so later decoding 323 // will see a blend of both. 324 r.Config = hcl.MergeBodies([]hcl.Body{r.Config, block.Body}) 325 326 default: 327 // Any other block types are ones we've reserved for future use, 328 // so they get a generic message. 329 diags = append(diags, &hcl.Diagnostic{ 330 Severity: hcl.DiagError, 331 Summary: "Reserved block type name in resource block", 332 Detail: fmt.Sprintf("The block type name %q is reserved for use by Durgaform in a future version.", block.Type), 333 Subject: &block.TypeRange, 334 }) 335 } 336 } 337 338 // Now we can validate the connection block references if there are any destroy provisioners. 339 // TODO: should we eliminate standalone connection blocks? 340 if r.Managed.Connection != nil { 341 for _, p := range r.Managed.Provisioners { 342 if p.When == ProvisionerWhenDestroy { 343 diags = append(diags, onlySelfRefs(r.Managed.Connection.Config)...) 344 break 345 } 346 } 347 } 348 349 return r, diags 350 } 351 352 func decodeDataBlock(block *hcl.Block, override bool) (*Resource, hcl.Diagnostics) { 353 var diags hcl.Diagnostics 354 r := &Resource{ 355 Mode: addrs.DataResourceMode, 356 Type: block.Labels[0], 357 Name: block.Labels[1], 358 DeclRange: block.DefRange, 359 TypeRange: block.LabelRanges[0], 360 } 361 362 content, remain, moreDiags := block.Body.PartialContent(dataBlockSchema) 363 diags = append(diags, moreDiags...) 364 r.Config = remain 365 366 if !hclsyntax.ValidIdentifier(r.Type) { 367 diags = append(diags, &hcl.Diagnostic{ 368 Severity: hcl.DiagError, 369 Summary: "Invalid data source name", 370 Detail: badIdentifierDetail, 371 Subject: &block.LabelRanges[0], 372 }) 373 } 374 if !hclsyntax.ValidIdentifier(r.Name) { 375 diags = append(diags, &hcl.Diagnostic{ 376 Severity: hcl.DiagError, 377 Summary: "Invalid data resource name", 378 Detail: badIdentifierDetail, 379 Subject: &block.LabelRanges[1], 380 }) 381 } 382 383 if attr, exists := content.Attributes["count"]; exists { 384 r.Count = attr.Expr 385 } 386 387 if attr, exists := content.Attributes["for_each"]; exists { 388 r.ForEach = attr.Expr 389 // Cannot have count and for_each on the same data block 390 if r.Count != nil { 391 diags = append(diags, &hcl.Diagnostic{ 392 Severity: hcl.DiagError, 393 Summary: `Invalid combination of "count" and "for_each"`, 394 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.`, 395 Subject: &attr.NameRange, 396 }) 397 } 398 } 399 400 if attr, exists := content.Attributes["provider"]; exists { 401 var providerDiags hcl.Diagnostics 402 r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr.Expr, "provider") 403 diags = append(diags, providerDiags...) 404 } 405 406 if attr, exists := content.Attributes["depends_on"]; exists { 407 deps, depsDiags := decodeDependsOn(attr) 408 diags = append(diags, depsDiags...) 409 r.DependsOn = append(r.DependsOn, deps...) 410 } 411 412 var seenEscapeBlock *hcl.Block 413 var seenLifecycle *hcl.Block 414 for _, block := range content.Blocks { 415 switch block.Type { 416 417 case "_": 418 if seenEscapeBlock != nil { 419 diags = append(diags, &hcl.Diagnostic{ 420 Severity: hcl.DiagError, 421 Summary: "Duplicate escaping block", 422 Detail: fmt.Sprintf( 423 "The special block type \"_\" can be used to force particular arguments to be interpreted as resource-type-specific rather than as meta-arguments, but each data block can have only one such block. The first escaping block was at %s.", 424 seenEscapeBlock.DefRange, 425 ), 426 Subject: &block.DefRange, 427 }) 428 continue 429 } 430 seenEscapeBlock = block 431 432 // When there's an escaping block its content merges with the 433 // existing config we extracted earlier, so later decoding 434 // will see a blend of both. 435 r.Config = hcl.MergeBodies([]hcl.Body{r.Config, block.Body}) 436 437 case "lifecycle": 438 if seenLifecycle != nil { 439 diags = append(diags, &hcl.Diagnostic{ 440 Severity: hcl.DiagError, 441 Summary: "Duplicate lifecycle block", 442 Detail: fmt.Sprintf("This resource already has a lifecycle block at %s.", seenLifecycle.DefRange), 443 Subject: block.DefRange.Ptr(), 444 }) 445 continue 446 } 447 seenLifecycle = block 448 449 lcContent, lcDiags := block.Body.Content(resourceLifecycleBlockSchema) 450 diags = append(diags, lcDiags...) 451 452 // All of the attributes defined for resource lifecycle are for 453 // managed resources only, so we can emit a common error message 454 // for any given attributes that HCL accepted. 455 for name, attr := range lcContent.Attributes { 456 diags = append(diags, &hcl.Diagnostic{ 457 Severity: hcl.DiagError, 458 Summary: "Invalid data resource lifecycle argument", 459 Detail: fmt.Sprintf("The lifecycle argument %q is defined only for managed resources (\"resource\" blocks), and is not valid for data resources.", name), 460 Subject: attr.NameRange.Ptr(), 461 }) 462 } 463 464 for _, block := range lcContent.Blocks { 465 switch block.Type { 466 case "precondition", "postcondition": 467 cr, moreDiags := decodeCheckRuleBlock(block, override) 468 diags = append(diags, moreDiags...) 469 470 moreDiags = cr.validateSelfReferences(block.Type, r.Addr()) 471 diags = append(diags, moreDiags...) 472 473 switch block.Type { 474 case "precondition": 475 r.Preconditions = append(r.Preconditions, cr) 476 case "postcondition": 477 r.Postconditions = append(r.Postconditions, cr) 478 } 479 default: 480 // The cases above should be exhaustive for all block types 481 // defined in the lifecycle schema, so this shouldn't happen. 482 panic(fmt.Sprintf("unexpected lifecycle sub-block type %q", block.Type)) 483 } 484 } 485 486 default: 487 // Any other block types are ones we're reserving for future use, 488 // but don't have any defined meaning today. 489 diags = append(diags, &hcl.Diagnostic{ 490 Severity: hcl.DiagError, 491 Summary: "Reserved block type name in data block", 492 Detail: fmt.Sprintf("The block type name %q is reserved for use by Durgaform in a future version.", block.Type), 493 Subject: block.TypeRange.Ptr(), 494 }) 495 } 496 } 497 498 return r, diags 499 } 500 501 // decodeReplaceTriggeredBy decodes and does basic validation of the 502 // replace_triggered_by expressions, ensuring they only contains references to 503 // a single resource, and the only extra variables are count.index or each.key. 504 func decodeReplaceTriggeredBy(expr hcl.Expression) ([]hcl.Expression, hcl.Diagnostics) { 505 // Since we are manually parsing the replace_triggered_by argument, we 506 // need to specially handle json configs, in which case the values will 507 // be json strings rather than hcl. To simplify parsing however we will 508 // decode the individual list elements, rather than the entire expression. 509 isJSON := hcljson.IsJSONExpression(expr) 510 511 exprs, diags := hcl.ExprList(expr) 512 513 for i, expr := range exprs { 514 if isJSON { 515 // We can abuse the hcl json api and rely on the fact that calling 516 // Value on a json expression with no EvalContext will return the 517 // raw string. We can then parse that as normal hcl syntax, and 518 // continue with the decoding. 519 v, ds := expr.Value(nil) 520 diags = diags.Extend(ds) 521 if diags.HasErrors() { 522 continue 523 } 524 525 expr, ds = hclsyntax.ParseExpression([]byte(v.AsString()), "", expr.Range().Start) 526 diags = diags.Extend(ds) 527 if diags.HasErrors() { 528 continue 529 } 530 // make sure to swap out the expression we're returning too 531 exprs[i] = expr 532 } 533 534 refs, refDiags := lang.ReferencesInExpr(expr) 535 for _, diag := range refDiags { 536 severity := hcl.DiagError 537 if diag.Severity() == tfdiags.Warning { 538 severity = hcl.DiagWarning 539 } 540 541 desc := diag.Description() 542 543 diags = append(diags, &hcl.Diagnostic{ 544 Severity: severity, 545 Summary: desc.Summary, 546 Detail: desc.Detail, 547 Subject: expr.Range().Ptr(), 548 }) 549 } 550 551 if refDiags.HasErrors() { 552 continue 553 } 554 555 resourceCount := 0 556 for _, ref := range refs { 557 switch sub := ref.Subject.(type) { 558 case addrs.Resource, addrs.ResourceInstance: 559 resourceCount++ 560 561 case addrs.ForEachAttr: 562 if sub.Name != "key" { 563 diags = append(diags, &hcl.Diagnostic{ 564 Severity: hcl.DiagError, 565 Summary: "Invalid each reference in replace_triggered_by expression", 566 Detail: "Only each.key may be used in replace_triggered_by.", 567 Subject: expr.Range().Ptr(), 568 }) 569 } 570 case addrs.CountAttr: 571 if sub.Name != "index" { 572 diags = append(diags, &hcl.Diagnostic{ 573 Severity: hcl.DiagError, 574 Summary: "Invalid count reference in replace_triggered_by expression", 575 Detail: "Only count.index may be used in replace_triggered_by.", 576 Subject: expr.Range().Ptr(), 577 }) 578 } 579 default: 580 // everything else should be simple traversals 581 diags = append(diags, &hcl.Diagnostic{ 582 Severity: hcl.DiagError, 583 Summary: "Invalid reference in replace_triggered_by expression", 584 Detail: "Only resources, count.index, and each.key may be used in replace_triggered_by.", 585 Subject: expr.Range().Ptr(), 586 }) 587 } 588 } 589 590 switch { 591 case resourceCount == 0: 592 diags = append(diags, &hcl.Diagnostic{ 593 Severity: hcl.DiagError, 594 Summary: "Invalid replace_triggered_by expression", 595 Detail: "Missing resource reference in replace_triggered_by expression.", 596 Subject: expr.Range().Ptr(), 597 }) 598 case resourceCount > 1: 599 diags = append(diags, &hcl.Diagnostic{ 600 Severity: hcl.DiagError, 601 Summary: "Invalid replace_triggered_by expression", 602 Detail: "Multiple resource references in replace_triggered_by expression.", 603 Subject: expr.Range().Ptr(), 604 }) 605 } 606 } 607 return exprs, diags 608 } 609 610 type ProviderConfigRef struct { 611 Name string 612 NameRange hcl.Range 613 Alias string 614 AliasRange *hcl.Range // nil if alias not set 615 616 // TODO: this may not be set in some cases, so it is not yet suitable for 617 // use outside of this package. We currently only use it for internal 618 // validation, but once we verify that this can be set in all cases, we can 619 // export this so providers don't need to be re-resolved. 620 // This same field is also added to the Provider struct. 621 providerType addrs.Provider 622 } 623 624 func decodeProviderConfigRef(expr hcl.Expression, argName string) (*ProviderConfigRef, hcl.Diagnostics) { 625 var diags hcl.Diagnostics 626 627 var shimDiags hcl.Diagnostics 628 expr, shimDiags = shimTraversalInString(expr, false) 629 diags = append(diags, shimDiags...) 630 631 traversal, travDiags := hcl.AbsTraversalForExpr(expr) 632 633 // AbsTraversalForExpr produces only generic errors, so we'll discard 634 // the errors given and produce our own with extra context. If we didn't 635 // get any errors then we might still have warnings, though. 636 if !travDiags.HasErrors() { 637 diags = append(diags, travDiags...) 638 } 639 640 if len(traversal) < 1 || len(traversal) > 2 { 641 // A provider reference was given as a string literal in the legacy 642 // configuration language and there are lots of examples out there 643 // showing that usage, so we'll sniff for that situation here and 644 // produce a specialized error message for it to help users find 645 // the new correct form. 646 if exprIsNativeQuotedString(expr) { 647 diags = append(diags, &hcl.Diagnostic{ 648 Severity: hcl.DiagError, 649 Summary: "Invalid provider configuration reference", 650 Detail: "A provider configuration reference must not be given in quotes.", 651 Subject: expr.Range().Ptr(), 652 }) 653 return nil, diags 654 } 655 656 diags = append(diags, &hcl.Diagnostic{ 657 Severity: hcl.DiagError, 658 Summary: "Invalid provider configuration reference", 659 Detail: fmt.Sprintf("The %s argument requires a provider type name, optionally followed by a period and then a configuration alias.", argName), 660 Subject: expr.Range().Ptr(), 661 }) 662 return nil, diags 663 } 664 665 // verify that the provider local name is normalized 666 name := traversal.RootName() 667 nameDiags := checkProviderNameNormalized(name, traversal[0].SourceRange()) 668 diags = append(diags, nameDiags...) 669 if diags.HasErrors() { 670 return nil, diags 671 } 672 673 ret := &ProviderConfigRef{ 674 Name: name, 675 NameRange: traversal[0].SourceRange(), 676 } 677 678 if len(traversal) > 1 { 679 aliasStep, ok := traversal[1].(hcl.TraverseAttr) 680 if !ok { 681 diags = append(diags, &hcl.Diagnostic{ 682 Severity: hcl.DiagError, 683 Summary: "Invalid provider configuration reference", 684 Detail: "Provider name must either stand alone or be followed by a period and then a configuration alias.", 685 Subject: traversal[1].SourceRange().Ptr(), 686 }) 687 return ret, diags 688 } 689 690 ret.Alias = aliasStep.Name 691 ret.AliasRange = aliasStep.SourceRange().Ptr() 692 } 693 694 return ret, diags 695 } 696 697 // Addr returns the provider config address corresponding to the receiving 698 // config reference. 699 // 700 // This is a trivial conversion, essentially just discarding the source 701 // location information and keeping just the addressing information. 702 func (r *ProviderConfigRef) Addr() addrs.LocalProviderConfig { 703 return addrs.LocalProviderConfig{ 704 LocalName: r.Name, 705 Alias: r.Alias, 706 } 707 } 708 709 func (r *ProviderConfigRef) String() string { 710 if r == nil { 711 return "<nil>" 712 } 713 if r.Alias != "" { 714 return fmt.Sprintf("%s.%s", r.Name, r.Alias) 715 } 716 return r.Name 717 } 718 719 var commonResourceAttributes = []hcl.AttributeSchema{ 720 { 721 Name: "count", 722 }, 723 { 724 Name: "for_each", 725 }, 726 { 727 Name: "provider", 728 }, 729 { 730 Name: "depends_on", 731 }, 732 } 733 734 var resourceBlockSchema = &hcl.BodySchema{ 735 Attributes: commonResourceAttributes, 736 Blocks: []hcl.BlockHeaderSchema{ 737 {Type: "locals"}, // reserved for future use 738 {Type: "lifecycle"}, 739 {Type: "connection"}, 740 {Type: "provisioner", LabelNames: []string{"type"}}, 741 {Type: "_"}, // meta-argument escaping block 742 }, 743 } 744 745 var dataBlockSchema = &hcl.BodySchema{ 746 Attributes: commonResourceAttributes, 747 Blocks: []hcl.BlockHeaderSchema{ 748 {Type: "lifecycle"}, 749 {Type: "locals"}, // reserved for future use 750 {Type: "_"}, // meta-argument escaping block 751 }, 752 } 753 754 var resourceLifecycleBlockSchema = &hcl.BodySchema{ 755 // We tell HCL that these elements are all valid for both "resource" 756 // and "data" lifecycle blocks, but the rules are actually more restrictive 757 // than that. We deal with that after decoding so that we can return 758 // more specific error messages than HCL would typically return itself. 759 Attributes: []hcl.AttributeSchema{ 760 { 761 Name: "create_before_destroy", 762 }, 763 { 764 Name: "prevent_destroy", 765 }, 766 { 767 Name: "ignore_changes", 768 }, 769 { 770 Name: "replace_triggered_by", 771 }, 772 }, 773 Blocks: []hcl.BlockHeaderSchema{ 774 {Type: "precondition"}, 775 {Type: "postcondition"}, 776 }, 777 }