blob: d35d23bde069553b6093263d1108bbe62bb4a9df [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package stackconfig
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// ProviderConfig is a provider configuration declared within a [Stack].
type ProviderConfig struct {
LocalAddr addrs.LocalProviderConfig
// ProviderAddr is populated only when loaded through either
// [LoadSingleStackConfig] or [LoadConfigDir], and contains the
// fully-qualified provider address corresponding to the local name
// given in field LocalAddr.
ProviderAddr addrs.Provider
// TODO: Figure out how we're going to retain the relevant subset of
// a provider configuration in the state so that we still have what
// we need to destroy any associated objects when a provider is removed
// from the configuration.
ForEach hcl.Expression
// Config is the body of the nested block containing the provider-specific
// configuration arguments, if specified. Some providers do not require
// explicit arguments and so the nested block is optional; this field
// will be nil if no block was included.
Config hcl.Body
ProviderNameRange tfdiags.SourceRange
DeclRange tfdiags.SourceRange
}
func decodeProviderConfigBlock(block *hcl.Block) (*ProviderConfig, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
ret := &ProviderConfig{
LocalAddr: addrs.LocalProviderConfig{
LocalName: block.Labels[0],
// we call this "name" in the stacks configuration language,
// but it's "Alias" here because we're reusing an address type
// made for the Terraform module language.
Alias: block.Labels[1],
},
ProviderNameRange: tfdiags.SourceRangeFromHCL(block.LabelRanges[0]),
DeclRange: tfdiags.SourceRangeFromHCL(block.DefRange),
}
if !hclsyntax.ValidIdentifier(ret.LocalAddr.LocalName) {
diags = diags.Append(invalidNameDiagnostic(
"Invalid provider local name",
block.LabelRanges[0],
))
return nil, diags
}
if !hclsyntax.ValidIdentifier(ret.LocalAddr.Alias) {
diags = diags.Append(invalidNameDiagnostic(
"Invalid provider configuration name",
block.LabelRanges[0],
))
return nil, diags
}
content, hclDiags := block.Body.Content(providerConfigBlockSchema)
diags = diags.Append(hclDiags)
if attr, ok := content.Attributes["for_each"]; ok {
ret.ForEach = attr.Expr
}
for _, block := range content.Blocks {
switch block.Type {
case "config":
if ret.Config != nil {
if !hclsyntax.ValidIdentifier(ret.LocalAddr.LocalName) {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Duplicate config block",
Detail: "A provider configuration block must contain only one nested \"config\" block.",
Subject: block.DefRange.Ptr(),
})
return nil, diags
}
continue
}
ret.Config = block.Body
default:
// Should not get here because the above should cover all
// block types declared in the schema.
panic(fmt.Sprintf("unhandled block type %q", block.Type))
}
}
return ret, diags
}
var providerConfigBlockSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: "for_each", Required: false},
},
Blocks: []hcl.BlockHeaderSchema{
{Type: "config"},
},
}