github.com/avenga/couper@v1.12.2/config/configload/merge.go (about) 1 package configload 2 3 import ( 4 "fmt" 5 "path" 6 "path/filepath" 7 "sort" 8 "strings" 9 10 "github.com/avenga/couper/config" 11 "github.com/avenga/couper/eval" 12 "github.com/hashicorp/hcl/v2" 13 "github.com/hashicorp/hcl/v2/hclsyntax" 14 "github.com/zclconf/go-cty/cty" 15 ) 16 17 const ( 18 errMultipleBackends = "Multiple definitions of backend are not allowed in %s." 19 errUniqueLabels = "All %s blocks must have unique labels." 20 ) 21 22 type namedBlocks map[string]*hclsyntax.Block 23 24 func mergeServers(bodies []*hclsyntax.Body, proxies map[string]*hclsyntax.Block) (hclsyntax.Blocks, error) { 25 type ( 26 apiDefinition struct { 27 labels []string 28 typeRange hcl.Range 29 labelRanges []hcl.Range 30 openBraceRange hcl.Range 31 closeBraceRange hcl.Range 32 attributes hclsyntax.Attributes 33 blocks namedBlocks 34 endpoints namedBlocks 35 errorHandler namedBlocks 36 } 37 spaDefinition struct { 38 labels []string 39 typeRange hcl.Range 40 labelRanges []hcl.Range 41 openBraceRange hcl.Range 42 closeBraceRange hcl.Range 43 attributes hclsyntax.Attributes 44 blocks namedBlocks 45 } 46 filesDefinition struct { 47 labels []string 48 typeRange hcl.Range 49 labelRanges []hcl.Range 50 openBraceRange hcl.Range 51 closeBraceRange hcl.Range 52 attributes hclsyntax.Attributes 53 blocks namedBlocks 54 } 55 56 tlsDefinition struct { 57 *hclsyntax.Block 58 typeBlocks map[string][]*hclsyntax.Block 59 preservedBlocks namedBlocks 60 } 61 62 namedAPIs map[string]*apiDefinition 63 namedSPAs map[string]*spaDefinition 64 namedFiles map[string]*filesDefinition 65 66 serverDefinition struct { 67 labels []string 68 typeRange hcl.Range 69 labelRanges []hcl.Range 70 openBraceRange hcl.Range 71 closeBraceRange hcl.Range 72 attributes hclsyntax.Attributes 73 apis namedAPIs 74 blocks namedBlocks 75 endpoints namedBlocks 76 files namedFiles 77 spas namedSPAs 78 tls *tlsDefinition 79 } 80 81 servers map[string]*serverDefinition 82 ) 83 84 /* 85 serverDefinition[<key>] = { 86 attributes = hclsyntax.Attributes 87 blocks[<name>] = hclsyntax.Block (cors, files, spa) 88 endpoints[<key>] = hclsyntax.Block 89 apis[<key>] = { 90 attributes = hclsyntax.Attributes 91 blocks[<name>] = hclsyntax.Block (cors) 92 endpoints[<key>] = hclsyntax.Block 93 error_handler[<key>] = hclsyntax.Block 94 } 95 spas[<key>] = { 96 attributes = hclsyntax.Attributes 97 blocks[<name>] = hclsyntax.Block (cors) 98 } 99 files[<key>] = { 100 attributes = hclsyntax.Attributes 101 blocks[<name>] = hclsyntax.Block (cors) 102 } 103 tls = { 104 blocks[<name>] = hclsyntax.Block (server_certificate|client_certificate) 105 } 106 } 107 */ 108 109 results := make(servers) 110 111 for _, body := range bodies { 112 uniqueServerLabels := make(map[string]struct{}) 113 114 for _, outerBlock := range body.Blocks { 115 if outerBlock.Type != server { 116 continue 117 } 118 119 var serverKey string 120 121 if len(outerBlock.Labels) > 0 { 122 serverKey = outerBlock.Labels[0] 123 } 124 125 if len(bodies) > 1 { 126 if _, ok := uniqueServerLabels[serverKey]; ok { 127 return nil, newMergeError(errUniqueLabels, outerBlock) 128 } 129 130 uniqueServerLabels[serverKey] = struct{}{} 131 } else { 132 // Create unique key for multiple server blocks inside a single config file. 133 serverKey += fmt.Sprintf("|%p", &serverKey) 134 } 135 136 if results[serverKey] == nil { 137 results[serverKey] = &serverDefinition{ 138 labels: outerBlock.Labels, 139 typeRange: outerBlock.TypeRange, 140 labelRanges: outerBlock.LabelRanges, 141 openBraceRange: outerBlock.OpenBraceRange, 142 closeBraceRange: outerBlock.CloseBraceRange, 143 attributes: make(hclsyntax.Attributes), 144 blocks: make(namedBlocks), 145 endpoints: make(namedBlocks), 146 apis: make(namedAPIs), 147 spas: make(namedSPAs), 148 files: make(namedFiles), 149 } 150 } 151 152 for name, attr := range outerBlock.Body.Attributes { 153 results[serverKey].attributes[name] = attr 154 } 155 156 for _, block := range outerBlock.Body.Blocks { 157 uniqueAPILabels, uniqueSPALabels, uniqueFilesLabels := make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{}) 158 159 if block.Type == endpoint { 160 if err := checkForMultipleBackends(block); err != nil { // Backend block inside a free endpoint block 161 return nil, err 162 } 163 164 if len(block.Labels) == 0 { 165 return nil, newMergeError(errUniqueLabels, block) 166 } 167 168 if err := addProxy(block, proxies); err != nil { 169 return nil, err 170 } 171 172 results[serverKey].endpoints[block.Labels[0]] = block 173 } else if block.Type == api { 174 var apiKey string 175 176 if len(block.Labels) > 0 { 177 apiKey = block.Labels[0] 178 } 179 180 if len(bodies) > 1 { 181 if _, ok := uniqueAPILabels[apiKey]; ok { 182 return nil, newMergeError(errUniqueLabels, block) 183 } 184 185 uniqueAPILabels[apiKey] = struct{}{} 186 } else { 187 // Create unique key for multiple api blocks inside a single config file. 188 apiKey += fmt.Sprintf("|%p", &apiKey) 189 } 190 191 if results[serverKey].apis[apiKey] == nil { 192 results[serverKey].apis[apiKey] = &apiDefinition{ 193 labels: block.Labels, 194 typeRange: block.TypeRange, 195 labelRanges: block.LabelRanges, 196 openBraceRange: block.OpenBraceRange, 197 closeBraceRange: block.CloseBraceRange, 198 attributes: make(hclsyntax.Attributes), 199 blocks: make(namedBlocks), 200 endpoints: make(namedBlocks), 201 errorHandler: make(namedBlocks), 202 } 203 } 204 205 for name, attr := range block.Body.Attributes { 206 results[serverKey].apis[apiKey].attributes[name] = attr 207 } 208 209 for _, subBlock := range block.Body.Blocks { 210 if subBlock.Type == endpoint { 211 if err := checkForMultipleBackends(subBlock); err != nil { 212 return nil, err 213 } 214 215 if len(subBlock.Labels) == 0 { 216 return nil, newMergeError(errUniqueLabels, subBlock) 217 } 218 219 if err := addProxy(subBlock, proxies); err != nil { 220 return nil, err 221 } 222 223 results[serverKey].apis[apiKey].endpoints[subBlock.Labels[0]] = subBlock 224 } else if subBlock.Type == errorHandler { 225 if err := checkForMultipleBackends(subBlock); err != nil { 226 return nil, err 227 } 228 229 ehKey := newErrorHandlerKey(subBlock) 230 231 results[serverKey].apis[apiKey].errorHandler[ehKey] = subBlock 232 } else { 233 results[serverKey].apis[apiKey].blocks[subBlock.Type] = subBlock 234 } 235 } 236 } else if block.Type == spa { 237 var spaKey string 238 239 if len(block.Labels) > 0 { 240 spaKey = block.Labels[0] 241 } 242 243 if len(bodies) > 1 { 244 if _, ok := uniqueSPALabels[spaKey]; ok { 245 return nil, newMergeError(errUniqueLabels, block) 246 } 247 248 uniqueSPALabels[spaKey] = struct{}{} 249 } else { 250 // Create unique key for multiple spa blocks inside a single config file. 251 spaKey += fmt.Sprintf("|%p", &spaKey) 252 } 253 254 if results[serverKey].spas[spaKey] == nil { 255 results[serverKey].spas[spaKey] = &spaDefinition{ 256 labels: block.Labels, 257 typeRange: block.TypeRange, 258 labelRanges: block.LabelRanges, 259 openBraceRange: block.OpenBraceRange, 260 closeBraceRange: block.CloseBraceRange, 261 attributes: make(hclsyntax.Attributes), 262 blocks: make(namedBlocks), 263 } 264 } 265 266 for name, attr := range block.Body.Attributes { 267 results[serverKey].spas[spaKey].attributes[name] = attr 268 } 269 270 for _, subBlock := range block.Body.Blocks { 271 results[serverKey].spas[spaKey].blocks[subBlock.Type] = subBlock 272 } 273 } else if block.Type == files { 274 var filesKey string 275 276 if len(block.Labels) > 0 { 277 filesKey = block.Labels[0] 278 } 279 280 if len(bodies) > 1 { 281 if _, ok := uniqueFilesLabels[filesKey]; ok { 282 return nil, newMergeError(errUniqueLabels, block) 283 } 284 285 uniqueFilesLabels[filesKey] = struct{}{} 286 } else { 287 // Create unique key for multiple files blocks inside a single config file. 288 filesKey += fmt.Sprintf("|%p", &filesKey) 289 } 290 291 if results[serverKey].files[filesKey] == nil { 292 results[serverKey].files[filesKey] = &filesDefinition{ 293 labels: block.Labels, 294 typeRange: block.TypeRange, 295 labelRanges: block.LabelRanges, 296 openBraceRange: block.OpenBraceRange, 297 closeBraceRange: block.CloseBraceRange, 298 attributes: make(hclsyntax.Attributes), 299 blocks: make(namedBlocks), 300 } 301 } 302 303 for name, attr := range block.Body.Attributes { 304 results[serverKey].files[filesKey].attributes[name] = attr 305 } 306 307 for _, subBlock := range block.Body.Blocks { 308 results[serverKey].files[filesKey].blocks[subBlock.Type] = subBlock 309 } 310 } else if block.Type == tls { 311 if results[serverKey].tls == nil { 312 results[serverKey].tls = &tlsDefinition{ 313 Block: block, 314 preservedBlocks: namedBlocks{}, 315 typeBlocks: map[string][]*hclsyntax.Block{}, 316 } 317 } 318 319 for name, attr := range block.Body.Attributes { 320 results[serverKey].tls.Body.Attributes[name] = attr 321 } 322 323 thisTypeBlocks := map[string][]*hclsyntax.Block{} 324 for _, subBlock := range block.Body.Blocks { 325 blockKey := subBlock.Type 326 if len(subBlock.Labels) > 0 { 327 blockKey += "_" + subBlock.Labels[0] 328 results[serverKey].tls.preservedBlocks[blockKey] = subBlock // override 329 } else { 330 thisTypeBlocks[subBlock.Type] = append(thisTypeBlocks[subBlock.Type], subBlock) 331 } 332 } 333 334 for t, v := range thisTypeBlocks { // reset per file 335 if len(v) > 0 { 336 results[serverKey].tls.typeBlocks[t] = v 337 } 338 339 } 340 341 } else { 342 results[serverKey].blocks[block.Type] = block 343 } 344 } 345 } 346 } 347 348 var mergedServers hclsyntax.Blocks 349 350 for _, name := range getSortedMapKeys(results) { 351 var serverBlocks hclsyntax.Blocks 352 serverBlock := results[name] 353 for _, blockName := range getSortedMapKeys(serverBlock.blocks) { 354 serverBlocks = append(serverBlocks, serverBlock.blocks[blockName]) 355 } 356 357 for _, blockName := range getSortedMapKeys(serverBlock.endpoints) { 358 serverBlocks = append(serverBlocks, serverBlock.endpoints[blockName]) 359 } 360 361 for _, apiBlock := range serverBlock.apis { 362 var apiBlocks hclsyntax.Blocks 363 364 for _, blockName := range getSortedMapKeys(apiBlock.blocks) { 365 apiBlocks = append(apiBlocks, apiBlock.blocks[blockName]) 366 } 367 368 for _, blockName := range getSortedMapKeys(apiBlock.endpoints) { 369 apiBlocks = append(apiBlocks, apiBlock.endpoints[blockName]) 370 } 371 372 for _, blockName := range getSortedMapKeys(apiBlock.errorHandler) { 373 apiBlocks = append(apiBlocks, apiBlock.errorHandler[blockName]) 374 } 375 376 mergedAPI := &hclsyntax.Block{ 377 Type: api, 378 Labels: apiBlock.labels, 379 Body: &hclsyntax.Body{ 380 Attributes: apiBlock.attributes, 381 Blocks: apiBlocks, 382 }, 383 TypeRange: apiBlock.typeRange, 384 LabelRanges: apiBlock.labelRanges, 385 OpenBraceRange: apiBlock.openBraceRange, 386 CloseBraceRange: apiBlock.closeBraceRange, 387 } 388 389 serverBlocks = append(serverBlocks, mergedAPI) 390 } 391 392 for _, blockName := range getSortedMapKeys(serverBlock.spas) { 393 spaBlock := serverBlock.spas[blockName] 394 var spaBlocks hclsyntax.Blocks 395 396 for _, bn := range getSortedMapKeys(spaBlock.blocks) { 397 spaBlocks = append(spaBlocks, spaBlock.blocks[bn]) 398 } 399 400 mergedSPA := &hclsyntax.Block{ 401 Type: spa, 402 Labels: spaBlock.labels, 403 Body: &hclsyntax.Body{ 404 Attributes: spaBlock.attributes, 405 Blocks: spaBlocks, 406 }, 407 TypeRange: spaBlock.typeRange, 408 LabelRanges: spaBlock.labelRanges, 409 OpenBraceRange: spaBlock.openBraceRange, 410 CloseBraceRange: spaBlock.closeBraceRange, 411 } 412 413 serverBlocks = append(serverBlocks, mergedSPA) 414 } 415 416 for _, blockName := range getSortedMapKeys(serverBlock.files) { 417 filesBlock := serverBlock.files[blockName] 418 var filesBlocks hclsyntax.Blocks 419 420 for _, bn := range getSortedMapKeys(filesBlock.blocks) { 421 filesBlocks = append(filesBlocks, filesBlock.blocks[bn]) 422 } 423 424 mergedFiles := &hclsyntax.Block{ 425 Type: files, 426 Labels: filesBlock.labels, 427 Body: &hclsyntax.Body{ 428 Attributes: filesBlock.attributes, 429 Blocks: filesBlocks, 430 }, 431 TypeRange: filesBlock.typeRange, 432 LabelRanges: filesBlock.labelRanges, 433 OpenBraceRange: filesBlock.openBraceRange, 434 CloseBraceRange: filesBlock.closeBraceRange, 435 } 436 437 serverBlocks = append(serverBlocks, mergedFiles) 438 } 439 440 if serverBlock.tls != nil { 441 var tlsCertificateBlocks hclsyntax.Blocks 442 for _, subType := range getSortedMapKeys(serverBlock.tls.typeBlocks) { 443 tlsCertificateBlocks = append(tlsCertificateBlocks, serverBlock.tls.typeBlocks[subType]...) 444 } 445 for _, k := range getSortedMapKeys(serverBlock.tls.preservedBlocks) { 446 tlsCertificateBlocks = append(tlsCertificateBlocks, serverBlock.tls.preservedBlocks[k]) 447 } 448 serverBlocks = append(serverBlocks, &hclsyntax.Block{ 449 Type: tls, 450 Body: &hclsyntax.Body{ 451 Attributes: serverBlock.tls.Body.Attributes, 452 Blocks: tlsCertificateBlocks, 453 }, 454 TypeRange: serverBlock.tls.TypeRange, 455 LabelRanges: serverBlock.tls.LabelRanges, 456 OpenBraceRange: serverBlock.tls.OpenBraceRange, 457 CloseBraceRange: serverBlock.tls.CloseBraceRange, 458 }) 459 } 460 461 mergedServer := &hclsyntax.Block{ 462 Type: server, 463 Labels: serverBlock.labels, 464 Body: &hclsyntax.Body{ 465 Attributes: serverBlock.attributes, 466 Blocks: serverBlocks, 467 }, 468 TypeRange: serverBlock.typeRange, 469 LabelRanges: serverBlock.labelRanges, 470 OpenBraceRange: serverBlock.openBraceRange, 471 CloseBraceRange: serverBlock.closeBraceRange, 472 } 473 474 mergedServers = append(mergedServers, mergedServer) 475 } 476 477 return mergedServers, nil 478 } 479 480 func mergeDefinitions(bodies []*hclsyntax.Body) (*hclsyntax.Block, map[string]*hclsyntax.Block, error) { 481 definitionsBlock := make(map[string]namedBlocks) 482 proxiesList := make(namedBlocks) 483 484 for _, body := range bodies { 485 for _, outerBlock := range body.Blocks { 486 if outerBlock.Type == definitions { 487 for _, innerBlock := range outerBlock.Body.Blocks { 488 if definitionsBlock[innerBlock.Type] == nil { 489 definitionsBlock[innerBlock.Type] = make(namedBlocks) 490 } 491 492 if innerBlock.Type == backend { 493 if err := checkForMultipleBackendsInBackend(innerBlock); err != nil { 494 return nil, nil, err 495 } 496 } 497 498 // Count the "backend" blocks and "backend" attributes to 499 // forbid multiple backend definitions. 500 501 var backends int 502 503 for _, block := range innerBlock.Body.Blocks { 504 if block.Type == errorHandler { 505 if err := checkForMultipleBackends(block); err != nil { 506 return nil, nil, err 507 } 508 } else if block.Type == backend { 509 backends++ 510 if err := checkForMultipleBackendsInBackend(block); err != nil { 511 return nil, nil, err 512 } 513 } 514 } 515 516 if _, ok := innerBlock.Body.Attributes[backend]; ok { 517 backends++ 518 } 519 520 if backends > 1 { 521 return nil, nil, newMergeError(errMultipleBackends, innerBlock) 522 } 523 524 if innerBlock.Type != proxy { 525 definitionsBlock[innerBlock.Type][innerBlock.Labels[0]] = innerBlock 526 } else { 527 label := innerBlock.Labels[0] 528 529 if attr, ok := innerBlock.Body.Attributes["name"]; ok { 530 name, err := attrStringValue(attr) 531 if err != nil { 532 return nil, nil, err 533 } 534 535 innerBlock.Labels[0] = name 536 537 delete(innerBlock.Body.Attributes, "name") 538 } else { 539 innerBlock.Labels[0] = config.DefaultNameLabel 540 } 541 542 proxiesList[label] = innerBlock 543 } 544 } 545 } 546 } 547 } 548 549 var blocks []*hclsyntax.Block 550 551 for _, name := range getSortedMapKeys(definitionsBlock) { 552 for _, label := range getSortedMapKeys(definitionsBlock[name]) { 553 blocks = append(blocks, definitionsBlock[name][label]) 554 } 555 } 556 557 return &hclsyntax.Block{ 558 Type: definitions, 559 Body: &hclsyntax.Body{ 560 Blocks: blocks, 561 }, 562 }, proxiesList, nil 563 } 564 565 func mergeDefaults(bodies []*hclsyntax.Body) (*hclsyntax.Block, error) { 566 attrs := make(hclsyntax.Attributes) 567 envVars := make(map[string]hclsyntax.Expression) 568 569 for _, body := range bodies { 570 for _, block := range body.Blocks { 571 if block.Type != defaults { 572 continue 573 } 574 575 for name, attr := range block.Body.Attributes { 576 if name != environmentVars { 577 attrs[name] = attr // Currently not used 578 continue 579 } 580 581 expObj, ok := attr.Expr.(*hclsyntax.ObjectConsExpr) 582 if !ok { 583 r := attr.Expr.Range() 584 return nil, newDiagErr(&r, fmt.Sprintf("%s must be object type", environmentVars)) 585 } 586 587 for _, item := range expObj.Items { 588 k := item.KeyExpr.(*hclsyntax.ObjectConsKeyExpr) 589 r := item.KeyExpr.Range() 590 var keyName string 591 switch exp := k.Wrapped.(type) { 592 case *hclsyntax.ScopeTraversalExpr: 593 if len(exp.Traversal) > 1 { 594 return nil, newDiagErr(&r, "unsupported key scope traversal expression") 595 } 596 keyName = exp.Traversal.RootName() 597 case *hclsyntax.TemplateExpr: 598 if !exp.IsStringLiteral() { 599 return nil, newDiagErr(&r, "unsupported key template expression") 600 } 601 v, _ := exp.Value(nil) 602 keyName = v.AsString() 603 default: 604 r := item.KeyExpr.Range() 605 return nil, newDiagErr(&r, "unsupported key expression") 606 } 607 608 envVars[keyName] = item.ValueExpr 609 } 610 } 611 } 612 } 613 614 if len(envVars) > 0 { 615 items := make([]hclsyntax.ObjectConsItem, 0) 616 for k, v := range envVars { 617 items = append(items, hclsyntax.ObjectConsItem{ 618 KeyExpr: &hclsyntax.ObjectConsKeyExpr{ 619 Wrapped: &hclsyntax.ScopeTraversalExpr{ 620 Traversal: hcl.Traversal{hcl.TraverseRoot{Name: k}}, 621 }, 622 ForceNonLiteral: false, 623 }, 624 ValueExpr: v, 625 }) 626 } 627 628 attrs[environmentVars] = &hclsyntax.Attribute{ 629 Name: environmentVars, 630 Expr: &hclsyntax.ObjectConsExpr{ 631 Items: items, 632 }, 633 } 634 } 635 636 defaultsBlock := &hclsyntax.Block{ 637 Type: defaults, 638 Body: &hclsyntax.Body{ 639 Attributes: attrs, 640 }, 641 } 642 return defaultsBlock, nil 643 } 644 645 func mergeSettings(bodies []*hclsyntax.Body) *hclsyntax.Block { 646 attrs := make(hclsyntax.Attributes) 647 648 for _, body := range bodies { 649 for _, block := range body.Blocks { 650 if block.Type == settings { 651 for name, attr := range block.Body.Attributes { 652 attrs[name] = attr 653 } 654 } 655 } 656 } 657 658 return &hclsyntax.Block{ 659 Type: settings, 660 Body: &hclsyntax.Body{ 661 Attributes: attrs, 662 }, 663 } 664 } 665 666 func newMergeError(msg string, block *hclsyntax.Block) error { 667 defRange := block.DefRange() 668 669 return hcl.Diagnostics{ 670 &hcl.Diagnostic{ 671 Severity: hcl.DiagError, 672 Summary: fmt.Sprintf(msg, block.Type), 673 Subject: &defRange, 674 }, 675 } 676 } 677 678 // absPath replaces the given attribute path expression with an absolute path 679 // related to its filename if not already an absolute one. 680 func absPath(attr *hclsyntax.Attribute) hclsyntax.Expression { 681 value, diags := attr.Expr.Value(envContext) 682 if diags.HasErrors() || strings.Index(value.AsString(), "/") == 0 { 683 return attr.Expr // Return unchanged in error cases and for absolute path values. 684 } 685 686 return &hclsyntax.LiteralValueExpr{ 687 Val: cty.StringVal( 688 path.Join(filepath.Dir(attr.SrcRange.Filename), value.AsString()), 689 ), 690 SrcRange: attr.SrcRange, 691 } 692 } 693 694 // checkForMultipleBackends searches for "backend" blocks inside a proxy or request block to 695 // count the "backend" 696 // blocks and "backend" attributes to forbid multiple backend definitions. 697 func checkForMultipleBackends(block *hclsyntax.Block) error { 698 for _, subBlock := range block.Body.Blocks { 699 if subBlock.Type == errorHandler { 700 return checkForMultipleBackends(subBlock) // Recursive call 701 } 702 703 if subBlock.Type != proxy && subBlock.Type != request { 704 continue 705 } 706 707 var backends int 708 709 for _, subSubBlock := range subBlock.Body.Blocks { 710 if subSubBlock.Type == backend { 711 backends++ 712 if err := checkForMultipleBackendsInBackend(subSubBlock); err != nil { 713 return err 714 } 715 } 716 } 717 718 if _, ok := subBlock.Body.Attributes[backend]; ok { 719 backends++ 720 } 721 722 if backends > 1 { 723 return newMergeError(errMultipleBackends, subBlock) 724 } 725 } 726 727 return nil 728 } 729 730 func checkForMultipleBackendsInBackend(block *hclsyntax.Block) error { 731 for _, subBlock := range block.Body.Blocks { 732 if subBlock.Type != oauth2 && subBlock.Type != tokenRequest { 733 continue 734 } 735 736 var backends int 737 738 for _, subSubBlock := range subBlock.Body.Blocks { 739 if subSubBlock.Type == backend { 740 backends++ 741 if err := checkForMultipleBackendsInBackend(subSubBlock); err != nil { // Recursive call 742 return err 743 } 744 } 745 } 746 747 if _, ok := subBlock.Body.Attributes[backend]; ok { 748 backends++ 749 } 750 751 if backends > 1 { 752 return newMergeError(errMultipleBackends, subBlock) 753 } 754 } 755 756 return nil 757 } 758 759 func addProxy(block *hclsyntax.Block, proxies map[string]*hclsyntax.Block) error { 760 if attr, ok := block.Body.Attributes[proxy]; ok { 761 reference, err := attrStringValue(attr) 762 if err != nil { 763 return err 764 } 765 766 proxyBlock, ok := proxies[reference] 767 if !ok { 768 sr := attr.Expr.StartRange() 769 770 return newDiagErr(&sr, fmt.Sprintf("referenced proxy %q is not defined", reference)) 771 } 772 773 delete(block.Body.Attributes, proxy) 774 775 block.Body.Blocks = append(block.Body.Blocks, proxyBlock) 776 } 777 778 return nil 779 } 780 781 func attrStringValue(attr *hclsyntax.Attribute) (string, error) { 782 v, err := eval.Value(nil, attr.Expr) 783 if err != nil { 784 return "", err 785 } 786 787 if v.Type() != cty.String { 788 sr := attr.Expr.StartRange() 789 return "", newDiagErr(&sr, fmt.Sprintf("%s must evaluate to string", attr.Name)) 790 } 791 792 return v.AsString(), nil 793 } 794 795 // newErrorHandlerKey returns a merge key based on a possible mixed error-kind format. 796 // "label1" and/or "label2 label3" results in "label1 label2 label3". 797 func newErrorHandlerKey(block *hclsyntax.Block) (key string) { 798 if len(block.Labels) == 0 { 799 return key 800 } 801 802 var sorted []string 803 for _, l := range block.Labels { 804 sorted = append(sorted, strings.Split(l, errorHandlerLabelSep)...) 805 } 806 sort.Strings(sorted) 807 808 return strings.Join(sorted, errorHandlerLabelSep) 809 } 810 811 func getSortedMapKeys[K string, V any](m map[K]V) []string { 812 var result []string 813 for k := range m { 814 result = append(result, string(k)) 815 } 816 sort.Strings(result) 817 return result 818 }