mirror of
https://github.com/hashicorp/terraform.git
synced 2026-03-21 18:10:30 -04:00
248 lines
7.6 KiB
Go
248 lines
7.6 KiB
Go
// Copyright IBM Corp. 2014, 2026
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package stackconfig
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/apparentlymart/go-versions/versions/constraints"
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/gohcl"
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
builtinProviders "github.com/hashicorp/terraform/internal/builtin/providers"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
)
|
|
|
|
type ProviderRequirements struct {
|
|
Requirements map[string]ProviderRequirement
|
|
|
|
DeclRange tfdiags.SourceRange
|
|
}
|
|
|
|
type ProviderRequirement struct {
|
|
LocalName string
|
|
|
|
Provider addrs.Provider
|
|
VersionConstraints constraints.IntersectionSpec
|
|
|
|
DeclRange tfdiags.SourceRange
|
|
}
|
|
|
|
func decodeProviderRequirementsBlock(block *hcl.Block) (*ProviderRequirements, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
var ret *ProviderRequirements
|
|
attrs, hclDiags := block.Body.JustAttributes()
|
|
diags = diags.Append(hclDiags)
|
|
|
|
// Include built-in providers, if not present
|
|
includeBuiltInProviders := func(pr *ProviderRequirements) *ProviderRequirements {
|
|
if pr == nil {
|
|
pr = &ProviderRequirements{
|
|
Requirements: make(map[string]ProviderRequirement, len(attrs)),
|
|
DeclRange: tfdiags.SourceRangeFromHCL(hcl.Range{}),
|
|
}
|
|
}
|
|
|
|
for providerName := range builtinProviders.BuiltInProviders() {
|
|
if _, ok := pr.Requirements[providerName]; !ok {
|
|
pr.Requirements[providerName] = ProviderRequirement{
|
|
LocalName: providerName,
|
|
Provider: addrs.NewBuiltInProvider(providerName),
|
|
}
|
|
}
|
|
}
|
|
|
|
return pr
|
|
}
|
|
|
|
if len(attrs) == 0 {
|
|
return includeBuiltInProviders(ret), diags
|
|
}
|
|
|
|
reverseMap := make(map[addrs.Provider]string)
|
|
|
|
ret = &ProviderRequirements{
|
|
Requirements: make(map[string]ProviderRequirement, len(attrs)),
|
|
DeclRange: tfdiags.SourceRangeFromHCL(block.DefRange),
|
|
}
|
|
for name, attr := range attrs {
|
|
if !hclsyntax.ValidIdentifier(name) {
|
|
diags = diags.Append(invalidNameDiagnostic(
|
|
"Invalid local name for provider",
|
|
attr.NameRange,
|
|
))
|
|
continue
|
|
}
|
|
if existing, exists := ret.Requirements[name]; exists {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Duplicate provider local name",
|
|
Detail: fmt.Sprintf("A provider requirement with local name %q was already declared at %s.", name, existing.DeclRange.StartString()),
|
|
Subject: attr.NameRange.Ptr(),
|
|
})
|
|
continue
|
|
}
|
|
declPairs, hclDiags := hcl.ExprMap(attr.Expr)
|
|
diags = diags.Append(hclDiags)
|
|
if hclDiags.HasErrors() {
|
|
continue
|
|
}
|
|
declAttrs := make(map[string]*hcl.KeyValuePair, len(declPairs))
|
|
for i := range declPairs {
|
|
pair := &declPairs[i]
|
|
name := hcl.ExprAsKeyword(pair.Key)
|
|
if name == "" {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid provider requirement attribute",
|
|
Detail: "All of the attributes of a required_providers entry must be simple keywords.",
|
|
Subject: pair.Key.Range().Ptr(),
|
|
})
|
|
continue
|
|
}
|
|
if existing, exists := declAttrs[name]; exists {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Duplicate attribute",
|
|
Detail: fmt.Sprintf("The attribute %q was already defined at %s.", name, existing.Key.Range()),
|
|
Subject: pair.Key.Range().Ptr(),
|
|
})
|
|
continue
|
|
}
|
|
declAttrs[name] = pair
|
|
}
|
|
|
|
var sourceAddrStr, versionConstraintsStr string
|
|
sourceAddrPair := declAttrs["source"]
|
|
versionConstraintsPair := declAttrs["version"]
|
|
delete(declAttrs, "source")
|
|
delete(declAttrs, "version")
|
|
|
|
if sourceAddrPair == nil {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Missing required attribute",
|
|
Detail: "All required_providers entries must include the attribute \"source\", giving the qualified provider source address to use.",
|
|
Subject: attr.Expr.StartRange().Ptr(),
|
|
})
|
|
continue
|
|
}
|
|
hclDiags = gohcl.DecodeExpression(sourceAddrPair.Value, nil, &sourceAddrStr)
|
|
diags = diags.Append(hclDiags)
|
|
if diags.HasErrors() {
|
|
continue
|
|
}
|
|
providerAddr, moreDiags := addrs.ParseProviderSourceString(sourceAddrStr)
|
|
// Ugh: ParseProviderSourceString returns sourceless diagnostics,
|
|
// so we need to postprocess the diagnostics to add source locations
|
|
// to them.
|
|
for _, diag := range moreDiags {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: diag.Severity().ToHCL(),
|
|
Summary: diag.Description().Summary,
|
|
Detail: diag.Description().Detail,
|
|
Subject: sourceAddrPair.Value.Range().Ptr(),
|
|
})
|
|
}
|
|
if moreDiags.HasErrors() {
|
|
continue
|
|
}
|
|
|
|
var versionConstraints constraints.IntersectionSpec
|
|
if !providerAddr.IsBuiltIn() {
|
|
if versionConstraintsPair == nil {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Missing required attribute",
|
|
Detail: "Each required_providers entry for an installable provider must include the attribute \"version\", specifying the provider versions that this stack is compatible with.",
|
|
Subject: attr.Expr.StartRange().Ptr(),
|
|
})
|
|
continue
|
|
}
|
|
for name, pair := range declAttrs {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid provider requirement attribute",
|
|
Detail: fmt.Sprintf("An attribute named %q is not expected here.", name),
|
|
Subject: pair.Key.Range().Ptr(),
|
|
})
|
|
continue
|
|
}
|
|
hclDiags = gohcl.DecodeExpression(versionConstraintsPair.Value, nil, &versionConstraintsStr)
|
|
diags = diags.Append(hclDiags)
|
|
if diags.HasErrors() {
|
|
continue
|
|
}
|
|
var err error
|
|
versionConstraints, err = constraints.ParseRubyStyleMulti(versionConstraintsStr)
|
|
if err != nil {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid version constraint",
|
|
Detail: fmt.Sprintf("Cannot use %q as a version constraint: %s.", versionConstraintsStr, err),
|
|
Subject: sourceAddrPair.Value.Range().Ptr(),
|
|
})
|
|
continue
|
|
}
|
|
} else {
|
|
if versionConstraintsPair != nil {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Unsupported attribute",
|
|
Detail: fmt.Sprintf("The provider %q is built in to Terraform, so does not support version constraints.", providerAddr.ForDisplay()),
|
|
Subject: attr.Expr.StartRange().Ptr(),
|
|
})
|
|
continue
|
|
}
|
|
}
|
|
|
|
if existingName, exists := reverseMap[providerAddr]; exists {
|
|
existing := ret.Requirements[existingName]
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Duplicate provider local name",
|
|
Detail: fmt.Sprintf(
|
|
"A requirement for provider %s was already declared with local name %q at %s.",
|
|
providerAddr, existingName, existing.DeclRange.StartString(),
|
|
),
|
|
Subject: attr.NameRange.Ptr(),
|
|
})
|
|
continue
|
|
}
|
|
|
|
ret.Requirements[name] = ProviderRequirement{
|
|
LocalName: name,
|
|
Provider: providerAddr,
|
|
VersionConstraints: versionConstraints,
|
|
DeclRange: tfdiags.SourceRangeFromHCL(attr.NameRange),
|
|
}
|
|
reverseMap[providerAddr] = name
|
|
}
|
|
|
|
return includeBuiltInProviders(ret), diags
|
|
}
|
|
|
|
func (pr *ProviderRequirements) ProviderForLocalName(localName string) (addrs.Provider, bool) {
|
|
if pr == nil {
|
|
return addrs.Provider{}, false
|
|
}
|
|
obj, ok := pr.Requirements[localName]
|
|
if !ok {
|
|
return addrs.Provider{}, false
|
|
}
|
|
return obj.Provider, true
|
|
}
|
|
|
|
func (pr *ProviderRequirements) LocalNameForProvider(providerAddr addrs.Provider) (string, bool) {
|
|
if pr == nil {
|
|
return "", false
|
|
}
|
|
for localName, obj := range pr.Requirements {
|
|
if obj.Provider == providerAddr {
|
|
return localName, true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|