| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package stackconfig | |
| import ( | |
| "github.com/apparentlymart/go-versions/versions/constraints" | |
| "github.com/hashicorp/go-slug/sourceaddrs" | |
| "github.com/hashicorp/hcl/v2" | |
| "github.com/hashicorp/hcl/v2/hclsyntax" | |
| "github.com/hashicorp/terraform/internal/configs" | |
| "github.com/hashicorp/terraform/internal/tfdiags" | |
| ) | |
| // EmbeddedStack describes a call to another stack configuration whose | |
| // declarations should be included as part of the overall stack configuration | |
| // tree. | |
| // | |
| // An embedded stack exists only as a child of another stack and doesn't have | |
| // its own independent identity outside of that calling stack. | |
| // | |
| // HCP Terraform offers a related concept of "linked stacks" where the | |
| // deployment configuration for one stack can refer to the outputs of another, | |
| // while the other stack retains its own independent identity and lifecycle, | |
| // but that concept only makes sense in an environment like HCP Terraform | |
| // where the stack outputs can be published for external consumption. | |
| type EmbeddedStack struct { | |
| Name string | |
| SourceAddr sourceaddrs.Source | |
| VersionConstraints constraints.IntersectionSpec | |
| SourceAddrRange, VersionConstraintsRange tfdiags.SourceRange | |
| // FinalSourceAddr is populated only when a configuration is loaded | |
| // through [LoadConfigDir], and in that case contains the finalized | |
| // address produced by resolving the SourceAddr field relative to | |
| // the address of the file where the component was declared. This | |
| // is the address to use if you intend to load the component's | |
| // root module from a source bundle. | |
| FinalSourceAddr sourceaddrs.FinalSource | |
| ForEach hcl.Expression | |
| // Inputs is an expression that should produce a value that can convert | |
| // to an object type derived from the child stack's input variable | |
| // declarations, and whose attribute values will then be used to populate | |
| // those input variables. | |
| Inputs hcl.Expression | |
| DependsOn []hcl.Traversal | |
| DeclRange tfdiags.SourceRange | |
| } | |
| func decodeEmbeddedStackBlock(block *hcl.Block) (*EmbeddedStack, tfdiags.Diagnostics) { | |
| var diags tfdiags.Diagnostics | |
| ret := &EmbeddedStack{ | |
| Name: block.Labels[0], | |
| DeclRange: tfdiags.SourceRangeFromHCL(block.DefRange), | |
| } | |
| if !hclsyntax.ValidIdentifier(ret.Name) { | |
| diags = diags.Append(invalidNameDiagnostic( | |
| "Invalid name for call to embedded stack", | |
| block.LabelRanges[0], | |
| )) | |
| return nil, diags | |
| } | |
| content, hclDiags := block.Body.Content(embeddedStackBlockSchema) | |
| diags = diags.Append(hclDiags) | |
| if hclDiags.HasErrors() { | |
| return nil, diags | |
| } | |
| sourceAddr, versionConstraints, moreDiags := decodeSourceAddrArguments( | |
| content.Attributes["source"], | |
| content.Attributes["version"], | |
| ) | |
| diags = diags.Append(moreDiags) | |
| if moreDiags.HasErrors() { | |
| return nil, diags | |
| } | |
| ret.SourceAddr = sourceAddr | |
| ret.VersionConstraints = versionConstraints | |
| ret.SourceAddrRange = tfdiags.SourceRangeFromHCL(content.Attributes["source"].Range) | |
| if content.Attributes["version"] != nil { | |
| ret.VersionConstraintsRange = tfdiags.SourceRangeFromHCL(content.Attributes["version"].Range) | |
| } | |
| // Now that we've populated the mandatory source location fields we can | |
| // safely return a partial ret if we encounter any further errors, as | |
| // long as we leave the other fields either unset or in some other | |
| // reasonable state for careful partial analysis. | |
| if attr, ok := content.Attributes["for_each"]; ok { | |
| ret.ForEach = attr.Expr | |
| } | |
| if attr, ok := content.Attributes["inputs"]; ok { | |
| ret.Inputs = attr.Expr | |
| } | |
| if attr, ok := content.Attributes["depends_on"]; ok { | |
| ret.DependsOn, hclDiags = configs.DecodeDependsOn(attr) | |
| diags = diags.Append(hclDiags) | |
| } | |
| return ret, diags | |
| } | |
| var embeddedStackBlockSchema = &hcl.BodySchema{ | |
| Attributes: []hcl.AttributeSchema{ | |
| {Name: "source", Required: true}, | |
| {Name: "version", Required: false}, | |
| {Name: "for_each", Required: false}, | |
| {Name: "inputs", Required: false}, | |
| {Name: "depends_on", Required: false}, | |
| }, | |
| } |