Partial revert of #1911, support provider for_each in aliases (#2121)

Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
Christian Mesh 2024-11-05 06:19:52 -05:00 committed by GitHub
parent 1155a3f711
commit e802b23200
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 289 additions and 612 deletions

View File

@ -16,7 +16,6 @@ ENHANCEMENTS:
* Added a help target to the Makefile. ([#1925](https://github.com/opentofu/opentofu/pull/1925))
* Added a simplified Build Process with a Makefile Target ([#1926](https://github.com/opentofu/opentofu/issues/1926))
* Ensures that the Makefile adheres to POSIX standards ([#1811](https://github.com/opentofu/opentofu/pull/1928))
* Added for-each support to providers ([#300](https://github.com/opentofu/opentofu/issues/300))
* Added consolidate warnings and errors flags ([#1894](https://github.com/opentofu/opentofu/pull/1894))
BUG FIXES:

View File

@ -950,6 +950,7 @@ func (c *Config) transformProviderConfigsForTest(run *TestRun, file *TestFile) (
// for by this run block.
for _, ref := range run.Providers {
testProvider, ok := file.getTestProviderOrMock(ref.InParent.String())
if !ok {
// Then this reference was invalid as we didn't have the
@ -965,16 +966,15 @@ func (c *Config) transformProviderConfigsForTest(run *TestRun, file *TestFile) (
}
next[ref.InChild.String()] = &Provider{
ProviderCommon: ProviderCommon{
Name: ref.InChild.Name,
NameRange: ref.InChild.NameRange,
Version: testProvider.Version,
Config: testProvider.Config,
DeclRange: testProvider.DeclRange,
IsMocked: testProvider.IsMocked,
MockResources: testProvider.MockResources,
},
Alias: ref.InChild.Alias,
Name: ref.InChild.Name,
NameRange: ref.InChild.NameRange,
Alias: ref.InChild.Alias,
AliasRange: ref.InChild.AliasRange,
Version: testProvider.Version,
Config: testProvider.Config,
DeclRange: testProvider.DeclRange,
IsMocked: testProvider.IsMocked,
MockResources: testProvider.MockResources,
}
}
@ -986,14 +986,13 @@ func (c *Config) transformProviderConfigsForTest(run *TestRun, file *TestFile) (
}
for _, mp := range file.MockProviders {
next[mp.moduleUniqueKey()] = &Provider{
ProviderCommon: ProviderCommon{
Name: mp.Name,
NameRange: mp.NameRange,
DeclRange: mp.DeclRange,
IsMocked: true,
MockResources: mp.MockResources,
},
Alias: mp.Alias,
Name: mp.Name,
NameRange: mp.NameRange,
Alias: mp.Alias,
AliasRange: mp.AliasRange,
DeclRange: mp.DeclRange,
IsMocked: true,
MockResources: mp.MockResources,
}
}
}

View File

@ -628,9 +628,7 @@ func TestTransformForTest(t *testing.T) {
}
provider := &Provider{
ProviderCommon: ProviderCommon{
Config: file.Body,
},
Config: file.Body,
}
parts := strings.Split(key, ".")

View File

@ -70,8 +70,8 @@ type Module struct {
// GetProviderConfig uses name and alias to find the respective Provider configuration.
func (m *Module) GetProviderConfig(name, alias string) (*Provider, bool) {
tp := &Provider{ProviderCommon: ProviderCommon{Name: name}, Alias: alias}
p, ok := m.ProviderConfigs[tp.Addr().StringCompact()]
tp := &Provider{Name: name, Alias: alias}
p, ok := m.ProviderConfigs[tp.moduleUniqueKey()]
return p, ok
}
@ -93,7 +93,7 @@ type File struct {
Backends []*Backend
CloudConfigs []*CloudConfig
ProviderConfigs []*ProviderBlock
ProviderConfigs []*Provider
ProviderMetas []*ProviderMeta
RequiredProviders []*RequiredProviders
Encryptions []*config.EncryptionConfig
@ -244,17 +244,6 @@ func NewModule(primaryFiles, overrideFiles []*File, call StaticModuleCall, sourc
mod.CloudConfig.eval = mod.StaticEvaluator
}
// Process all providers with the static context
for _, file := range primaryFiles {
fileDiags := mod.appendFileProviders(file)
diags = append(diags, fileDiags...)
}
for _, file := range overrideFiles {
fileDiags := mod.mergeFileProviders(file)
diags = append(diags, fileDiags...)
}
// Process all module calls now that we have the static context
for _, mc := range mod.ModuleCalls {
mDiags := mc.decodeStaticFields(mod.StaticEvaluator)
@ -328,6 +317,29 @@ func (m *Module) appendFile(file *File) hcl.Diagnostics {
})
}
for _, pc := range file.ProviderConfigs {
key := pc.moduleUniqueKey()
if existing, exists := m.ProviderConfigs[key]; exists {
if existing.Alias == "" {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Duplicate provider configuration",
Detail: fmt.Sprintf("A default (non-aliased) provider configuration for %q was already given at %s. If multiple configurations are required, set the \"alias\" argument for alternative configurations.", existing.Name, existing.DeclRange),
Subject: &pc.DeclRange,
})
} else {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Duplicate provider configuration",
Detail: fmt.Sprintf("A provider configuration for %q with alias %q was already given at %s. Each configuration for the same provider must have a distinct alias.", existing.Name, existing.Alias, existing.DeclRange),
Subject: &pc.DeclRange,
})
}
continue
}
m.ProviderConfigs[key] = pc
}
for _, pm := range file.ProviderMetas {
provider := m.ProviderForLocalConfig(addrs.LocalProviderConfig{LocalName: pm.Provider})
if existing, exists := m.ProviderMetas[provider]; exists {
@ -578,6 +590,38 @@ func (m *Module) mergeFile(file *File) hcl.Diagnostics {
}
}
for _, pc := range file.ProviderConfigs {
key := pc.moduleUniqueKey()
existing, exists := m.ProviderConfigs[key]
if pc.Alias == "" {
// We allow overriding a non-existing _default_ provider configuration
// because the user model is that an absent provider configuration
// implies an empty provider configuration, which is what the user
// is therefore overriding here.
if exists {
mergeDiags := existing.merge(pc)
diags = append(diags, mergeDiags...)
} else {
m.ProviderConfigs[key] = pc
}
} else {
// For aliased providers, there must be a base configuration to
// override. This allows us to detect and report alias typos
// that might otherwise cause the override to not apply.
if !exists {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing base provider configuration for override",
Detail: fmt.Sprintf("There is no %s provider configuration with the alias %q. An override file can only override an aliased provider configuration that was already defined in a primary configuration file.", pc.Name, pc.Alias),
Subject: &pc.DeclRange,
})
continue
}
mergeDiags := existing.merge(pc)
diags = append(diags, mergeDiags...)
}
}
if len(file.Encryptions) != 0 {
switch len(file.Encryptions) {
case 1:
@ -716,85 +760,6 @@ func (m *Module) mergeFile(file *File) hcl.Diagnostics {
return diags
}
func (m *Module) appendFileProviders(file *File) hcl.Diagnostics {
var diags hcl.Diagnostics
for _, pci := range file.ProviderConfigs {
pcs, decodeDiags := pci.decodeStaticFields(m.StaticEvaluator)
diags = append(diags, decodeDiags...)
if decodeDiags.HasErrors() {
continue
}
for _, pc := range pcs {
key := pc.Addr().StringCompact()
if existing, exists := m.ProviderConfigs[key]; exists {
if existing.Alias == "" {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Duplicate provider configuration",
Detail: fmt.Sprintf("A default (non-aliased) provider configuration for %q was already given at %s. If multiple configurations are required, set the \"alias\" argument for alternative configurations.", existing.Name, existing.DeclRange),
Subject: &pc.DeclRange,
})
} else {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Duplicate provider configuration",
Detail: fmt.Sprintf("A provider configuration for %q with alias %q was already given at %s. Each configuration for the same provider must have a distinct alias.", existing.Name, existing.Alias, existing.DeclRange),
Subject: &pc.DeclRange,
})
}
continue
}
m.ProviderConfigs[key] = pc
}
}
return diags
}
func (m *Module) mergeFileProviders(file *File) hcl.Diagnostics {
var diags hcl.Diagnostics
for _, pci := range file.ProviderConfigs {
pcs, decodeDiags := pci.decodeStaticFields(m.StaticEvaluator)
diags = append(diags, decodeDiags...)
if decodeDiags.HasErrors() {
continue
}
for _, pc := range pcs {
key := pc.Addr().StringCompact()
existing, exists := m.ProviderConfigs[key]
if pc.Alias == "" {
// We allow overriding a non-existing _default_ provider configuration
// because the user model is that an absent provider configuration
// implies an empty provider configuration, which is what the user
// is therefore overriding here.
if exists {
mergeDiags := existing.merge(pc)
diags = append(diags, mergeDiags...)
} else {
m.ProviderConfigs[key] = pc
}
} else {
// For aliased providers, there must be a base configuration to
// override. This allows us to detect and report alias typos
// that might otherwise cause the override to not apply.
if !exists {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing base provider configuration for override",
Detail: fmt.Sprintf("There is no %s provider configuration with the alias %q. An override file can only override an aliased provider configuration that was already defined in a primary configuration file.", pc.Name, pc.Alias),
Subject: &pc.DeclRange,
})
continue
}
mergeDiags := existing.merge(pc)
diags = append(diags, mergeDiags...)
}
}
}
return diags
}
// gatherProviderLocalNames is a helper function that populatesA a map of
// provider FQNs -> provider local names. This information is useful for
// user-facing output, which should include both the FQN and LocalName. It must

View File

@ -13,15 +13,17 @@ import (
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/instances"
"github.com/opentofu/opentofu/internal/lang/evalchecks"
"github.com/opentofu/opentofu/internal/tfdiags"
)
// ProviderCommon is the common fields between a Provider Block and a Provider
type ProviderCommon struct {
Name string
NameRange hcl.Range
// Provider represents a "provider" block in a module or file. A provider
// block is a provider configuration, and there can be zero or more
// configurations for each actual provider.
type Provider struct {
Name string
NameRange hcl.Range
Alias string
AliasRange *hcl.Range // nil if no alias set
Version VersionConstraint
@ -42,105 +44,7 @@ type ProviderCommon struct {
MockResources []*MockResource
}
// ProviderBlock represents a "provider" block in a module or file. A provider
// block is a provider configuration
type ProviderBlock struct {
ProviderCommon
AliasExpr hcl.Expression // nil if no alias set
AliasRange *hcl.Range // nil if no alias set
ForEach hcl.Expression
}
// Provider represents an instance of a "provider" block. Created after
// the exvaluation of a provider block containing the specific data
// for each instance derived from the evaluation
type Provider struct {
ProviderCommon
Alias string
InstanceData instances.RepetitionData
}
// ParseProviderConfigCompact parses the given absolute traversal as a relative
// provider address in compact form. The following are examples of traversals
// that can be successfully parsed as compact relative provider configuration
// addresses:
//
// - aws
// - aws.foo
//
// This function will panic if given a relative traversal.
//
// If the returned diagnostics contains errors then the result value is invalid
// and must not be used.
func ParseProviderConfigCompact(traversal hcl.Traversal) (addrs.LocalProviderConfig, tfdiags.Diagnostics) {
// added as a const to keep the linter happy
const providerAddrMaxTraversal = 2
var diags tfdiags.Diagnostics
ret := addrs.LocalProviderConfig{
LocalName: traversal.RootName(),
}
if len(traversal) < providerAddrMaxTraversal {
// Just a type name, then.
return ret, diags
}
aliasStep := traversal[1]
switch ts := aliasStep.(type) {
case hcl.TraverseAttr:
ret.Alias = ts.Name
return ret, diags
default:
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider configuration address",
Detail: "The provider type name must either stand alone or be followed by an alias name separated with a dot.",
Subject: aliasStep.SourceRange().Ptr(),
})
}
if len(traversal) > providerAddrMaxTraversal {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider configuration address",
Detail: "Extraneous extra operators after provider configuration address.",
Subject: traversal[providerAddrMaxTraversal:].SourceRange().Ptr(),
})
}
return ret, diags
}
// ParseProviderConfigCompactStr is a helper wrapper around ParseProviderConfigCompact
// that takes a string and parses it with the HCL native syntax traversal parser
// before interpreting it.
//
// This should be used only in specialized situations since it will cause the
// created references to not have any meaningful source location information.
// If a reference string is coming from a source that should be identified in
// error messages then the caller should instead parse it directly using a
// suitable function from the HCL API and pass the traversal itself to
// ParseProviderConfigCompact.
//
// Error diagnostics are returned if either the parsing fails or the analysis
// of the traversal fails. There is no way for the caller to distinguish the
// two kinds of diagnostics programmatically. If error diagnostics are returned
// then the returned address is invalid.
func ParseProviderConfigCompactStr(str string) (addrs.LocalProviderConfig, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
diags = diags.Append(parseDiags)
if parseDiags.HasErrors() {
return addrs.LocalProviderConfig{}, diags
}
addr, addrDiags := ParseProviderConfigCompact(traversal)
diags = diags.Append(addrDiags)
return addr, diags
}
func decodeProviderBlock(block *hcl.Block) (*ProviderBlock, hcl.Diagnostics) {
func decodeProviderBlock(block *hcl.Block) (*Provider, hcl.Diagnostics) {
var diags hcl.Diagnostics
content, config, moreDiags := block.Body.PartialContent(providerBlockSchema)
@ -158,31 +62,25 @@ func decodeProviderBlock(block *hcl.Block) (*ProviderBlock, hcl.Diagnostics) {
return nil, diags
}
provider := &ProviderBlock{
ProviderCommon: ProviderCommon{
Name: name,
NameRange: block.LabelRanges[0],
Config: config,
DeclRange: block.DefRange,
},
provider := &Provider{
Name: name,
NameRange: block.LabelRanges[0],
Config: config,
DeclRange: block.DefRange,
}
if attr, exists := content.Attributes["alias"]; exists {
provider.AliasExpr = attr.Expr
valDiags := gohcl.DecodeExpression(attr.Expr, nil, &provider.Alias)
diags = append(diags, valDiags...)
provider.AliasRange = attr.Expr.Range().Ptr()
}
if attr, exists := content.Attributes["for_each"]; exists {
provider.ForEach = attr.Expr
}
if provider.AliasExpr != nil && provider.ForEach != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: `Invalid combination of "alias" and "for_each"`,
Detail: `The "alias" and "for_each" arguments are mutually-exclusive, only one may be used.`,
Subject: provider.AliasExpr.Range().Ptr(),
})
if !hclsyntax.ValidIdentifier(provider.Alias) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider configuration alias",
Detail: fmt.Sprintf("An alias must be a valid name. %s", badIdentifierDetail),
})
}
}
if attr, exists := content.Attributes["version"]; exists {
@ -197,8 +95,17 @@ func decodeProviderBlock(block *hcl.Block) (*ProviderBlock, hcl.Diagnostics) {
diags = append(diags, versionDiags...)
}
reserveredDiags := checkReservedNames(content)
diags = append(diags, reserveredDiags...)
// Reserved attribute names
for _, name := range []string{"count", "depends_on", "for_each", "source"} {
if attr, exists := content.Attributes[name]; exists {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Reserved argument name in provider block",
Detail: fmt.Sprintf("The provider argument name %q is reserved for use by OpenTofu in a future version.", name),
Subject: &attr.NameRange,
})
}
}
var seenEscapeBlock *hcl.Block
for _, block := range content.Blocks {
@ -238,20 +145,122 @@ func decodeProviderBlock(block *hcl.Block) (*ProviderBlock, hcl.Diagnostics) {
return provider, diags
}
func checkReservedNames(content *hcl.BodyContent) hcl.Diagnostics {
var diags hcl.Diagnostics
// Reserved attribute names
for _, name := range []string{"depends_on", "source", "count"} {
if attr, exists := content.Attributes[name]; exists {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Reserved argument name in provider block",
Detail: fmt.Sprintf("The provider argument name %q is reserved for use by OpenTofu in a future version.", name),
Subject: &attr.NameRange,
})
}
// Addr returns the address of the receiving provider configuration, relative
// to its containing module.
func (p *Provider) Addr() addrs.LocalProviderConfig {
return addrs.LocalProviderConfig{
LocalName: p.Name,
Alias: p.Alias,
}
return diags
}
func (p *Provider) moduleUniqueKey() string {
if p.Alias != "" {
return fmt.Sprintf("%s.%s", p.Name, p.Alias)
}
return p.Name
}
// ParseProviderConfigCompact parses the given absolute traversal as a relative
// provider address in compact form. The following are examples of traversals
// that can be successfully parsed as compact relative provider configuration
// addresses:
//
// - aws
// - aws.foo
//
// This function will panic if given a relative traversal.
//
// If the returned diagnostics contains errors then the result value is invalid
// and must not be used.
func ParseProviderConfigCompact(traversal hcl.Traversal) (addrs.LocalProviderConfig, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
ret := addrs.LocalProviderConfig{
LocalName: traversal.RootName(),
}
if len(traversal) < 2 {
// Just a type name, then.
return ret, diags
}
aliasStep := traversal[1]
switch ts := aliasStep.(type) {
case hcl.TraverseAttr:
ret.Alias = ts.Name
return ret, diags
default:
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider configuration address",
Detail: "The provider type name must either stand alone or be followed by an alias name separated with a dot.",
Subject: aliasStep.SourceRange().Ptr(),
})
}
if len(traversal) > 2 {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider configuration address",
Detail: "Extraneous extra operators after provider configuration address.",
Subject: traversal[2:].SourceRange().Ptr(),
})
}
return ret, diags
}
// ParseProviderConfigCompactStr is a helper wrapper around ParseProviderConfigCompact
// that takes a string and parses it with the HCL native syntax traversal parser
// before interpreting it.
//
// This should be used only in specialized situations since it will cause the
// created references to not have any meaningful source location information.
// If a reference string is coming from a source that should be identified in
// error messages then the caller should instead parse it directly using a
// suitable function from the HCL API and pass the traversal itself to
// ParseProviderConfigCompact.
//
// Error diagnostics are returned if either the parsing fails or the analysis
// of the traversal fails. There is no way for the caller to distinguish the
// two kinds of diagnostics programmatically. If error diagnostics are returned
// then the returned address is invalid.
func ParseProviderConfigCompactStr(str string) (addrs.LocalProviderConfig, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
diags = diags.Append(parseDiags)
if parseDiags.HasErrors() {
return addrs.LocalProviderConfig{}, diags
}
addr, addrDiags := ParseProviderConfigCompact(traversal)
diags = diags.Append(addrDiags)
return addr, diags
}
var providerBlockSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "alias",
},
{
Name: "version",
},
// Attribute names reserved for future expansion.
{Name: "count"},
{Name: "depends_on"},
{Name: "for_each"},
{Name: "source"},
},
Blocks: []hcl.BlockHeaderSchema{
{Type: "_"}, // meta-argument escaping block
// The rest of these are reserved for future expansion.
{Type: "lifecycle"},
{Type: "locals"},
},
}
// checkProviderNameNormalized verifies that the given string is already
@ -281,124 +290,3 @@ func checkProviderNameNormalized(name string, declrange hcl.Range) hcl.Diagnosti
}
return diags
}
// Addr returns the address of the receiving provider configuration, relative
// to its containing module.
func (p *Provider) Addr() addrs.LocalProviderConfig {
return addrs.LocalProviderConfig{
LocalName: p.Name,
Alias: p.Alias,
}
}
func (p *ProviderBlock) decodeStaticFields(eval *StaticEvaluator) ([]*Provider, hcl.Diagnostics) {
var diags hcl.Diagnostics
if p.ForEach != nil {
return p.generateForEachProviders(eval)
}
result := Provider{ProviderCommon: p.ProviderCommon}
if p.AliasExpr != nil {
if eval != nil {
valDiags := eval.DecodeExpression(p.AliasExpr, StaticIdentifier{
Module: eval.call.addr,
Subject: fmt.Sprintf("provider.%s.alias", p.Name),
DeclRange: p.AliasExpr.Range(),
}, &result.Alias)
diags = append(diags, valDiags...)
} else {
// Test files don't have a static context
valDiags := gohcl.DecodeExpression(p.AliasExpr, nil, &result.Alias)
diags = append(diags, valDiags...)
}
if diags.HasErrors() {
return nil, diags
}
if !hclsyntax.ValidIdentifier(result.Alias) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider configuration alias",
Detail: fmt.Sprintf("Alias %q must be a valid name. %s", result.Alias, badIdentifierDetail),
Subject: p.AliasExpr.Range().Ptr(),
})
}
}
return []*Provider{&result}, diags
}
func (p *ProviderBlock) generateForEachProviders(eval *StaticEvaluator) ([]*Provider, hcl.Diagnostics) {
var diags hcl.Diagnostics
if eval == nil {
return nil, diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Iteration not allowed in test files",
Detail: "for_each was declared as a provider attribute in a test file",
Subject: p.ForEach.Range().Ptr(),
})
}
forEachRefsFunc := func(refs []*addrs.Reference) (*hcl.EvalContext, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
evalContext, evalDiags := eval.EvalContext(StaticIdentifier{
Module: eval.call.addr,
Subject: fmt.Sprintf("provider.%s.for_each", p.Name),
DeclRange: p.ForEach.Range(),
}, refs)
return evalContext, diags.Append(evalDiags)
}
forVal, evalDiags := evalchecks.EvaluateForEachExpression(p.ForEach, forEachRefsFunc)
diags = append(diags, evalDiags.ToHCL()...)
if evalDiags.HasErrors() {
return nil, diags
}
var out []*Provider
for k, v := range forVal {
if !hclsyntax.ValidIdentifier(k) {
return nil, diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid for_each key alias",
Detail: fmt.Sprintf("Alias %q must be a valid name. %s", k, badIdentifierDetail),
Subject: p.ForEach.Range().Ptr(),
})
}
out = append(out, &Provider{
ProviderCommon: p.ProviderCommon,
Alias: k,
InstanceData: instances.RepetitionData{
EachValue: v,
},
})
}
return out, diags
}
var providerBlockSchema = &hcl.BodySchema{ //nolint: gochecknoglobals // pre-existing code
Attributes: []hcl.AttributeSchema{
{
Name: "alias",
},
{
Name: "version",
},
{
Name: "for_each",
},
// Attribute names reserved for future expansion.
{Name: "count"},
{Name: "depends_on"},
{Name: "source"},
},
Blocks: []hcl.BlockHeaderSchema{
{Type: "_"}, // meta-argument escaping block
// The rest of these are reserved for future expansion.
{Type: "lifecycle"},
{Type: "locals"},
},
}

View File

@ -1,76 +0,0 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package configs
import (
"testing"
"github.com/opentofu/opentofu/internal/addrs"
)
const (
providerTestName = "local"
)
func TestNewModule_provider_foreach(t *testing.T) {
mod, diags := testModuleFromDir("testdata/providers_foreach")
if diags.HasErrors() {
t.Fatal(diags.Error())
}
p := addrs.NewProvider(addrs.DefaultProviderRegistryHost, "hashicorp", providerTestName)
if name, exists := mod.ProviderLocalNames[p]; !exists {
t.Fatal("provider FQN hashicorp/local not found")
} else if name != providerTestName {
t.Fatalf("provider localname mismatch: got %s, want %s", name, providerTestName)
}
if len(mod.ProviderConfigs) != 3 {
t.Fatalf("incorrect number of providers: got %d, expected: %d", len(mod.ProviderConfigs), 3)
}
_, foundDev := mod.GetProviderConfig("foo-test", "dev")
if !foundDev {
t.Fatal("unable to find dev provider")
}
_, foundTest := mod.GetProviderConfig("foo-test", "test")
if !foundTest {
t.Fatal("unable to find test provider")
}
_, foundProd := mod.GetProviderConfig("foo-test", "prod")
if !foundProd {
t.Fatal("unable to find prod provider")
}
}
func TestNewModule_provider_invalid_name(t *testing.T) {
mod, diags := testModuleFromDir("testdata/providers_iteration_invalid_name")
if !diags.HasErrors() {
t.Fatal("expected error")
}
expected := "Invalid for_each key alias"
expectedDetail := "Alias \"0\" must be a valid name. A name must start with a letter or underscore and may contain only letters, digits, underscores, and dashes."
if gotErr := diags[0].Summary; gotErr != expected {
t.Errorf("wrong error, got %q, want %q", gotErr, expected)
}
if gotErr := diags[0].Detail; gotErr != expectedDetail {
t.Errorf("wrong error, got %q, want %q", gotErr, expectedDetail)
}
p := addrs.NewProvider(addrs.DefaultProviderRegistryHost, "hashicorp", providerTestName)
if name, exists := mod.ProviderLocalNames[p]; !exists {
t.Fatal("provider FQN hashicorp/local not found")
} else if name != providerTestName {
t.Fatalf("provider localname mismatch: got %s, want %s", name, providerTestName)
}
if len(mod.ProviderConfigs) != 0 {
t.Fatalf("incorrect number of providers: got %d, expected: %d", len(mod.ProviderConfigs), 0)
}
}

View File

@ -30,10 +30,10 @@ func TestProviderReservedNames(t *testing.T) {
`config.tf:4,13-20: Version constraints inside provider configuration blocks are deprecated; OpenTofu 0.13 and earlier allowed provider version constraints inside the provider configuration block, but that is now deprecated and will be removed in a future version of OpenTofu. To silence this warning, move the provider version constraint into the required_providers block.`,
`config.tf:10,3-8: Reserved argument name in provider block; The provider argument name "count" is reserved for use by OpenTofu in a future version.`,
`config.tf:11,3-13: Reserved argument name in provider block; The provider argument name "depends_on" is reserved for use by OpenTofu in a future version.`,
`config.tf:12,3-11: Reserved argument name in provider block; The provider argument name "for_each" is reserved for use by OpenTofu in a future version.`,
`config.tf:14,3-12: Reserved block type name in provider block; The block type name "lifecycle" is reserved for use by OpenTofu in a future version.`,
`config.tf:15,3-9: Reserved block type name in provider block; The block type name "locals" is reserved for use by OpenTofu in a future version.`,
`config.tf:13,3-9: Reserved argument name in provider block; The provider argument name "source" is reserved for use by OpenTofu in a future version.`,
`config.tf:3,13-18: Invalid combination of "alias" and "for_each"; The "alias" and "for_each" arguments are mutually-exclusive, only one may be used.`,
})
}

View File

@ -94,7 +94,7 @@ func validateProviderConfigsForTests(cfg *Config) (diags hcl.Diagnostics) {
Summary: "Provider type mismatch",
Detail: fmt.Sprintf(
"The provider %q in %s represents provider %q, but %q in the root module represents %q.\n\nThis means the provider definition for %q within %s, or other provider definitions with the same name, have been referenced by multiple run blocks and assigned to different provider types.",
provider.Addr().StringCompact(), name, providerType, requirement.Name, requirement.Type, provider.Addr().StringCompact(), name),
provider.moduleUniqueKey(), name, providerType, requirement.Name, requirement.Type, provider.moduleUniqueKey(), name),
Subject: provider.DeclRange.Ptr(),
})
}
@ -114,7 +114,7 @@ func validateProviderConfigsForTests(cfg *Config) (diags hcl.Diagnostics) {
Summary: "Provider type mismatch",
Detail: fmt.Sprintf(
"The provider %q in %s represents provider %q, but %q in the root module represents %q.\n\nThis means the provider definition for %q within %s, or other provider definitions with the same name, have been referenced by multiple run blocks and assigned to different provider types.",
provider.Addr().StringCompact(), name, providerType, alias.StringCompact(), requirement.Type, provider.Addr().StringCompact(), name),
provider.moduleUniqueKey(), name, providerType, alias.StringCompact(), requirement.Type, provider.moduleUniqueKey(), name),
Subject: provider.DeclRange.Ptr(),
})
}
@ -129,7 +129,8 @@ func validateProviderConfigsForTests(cfg *Config) (diags hcl.Diagnostics) {
providerType = addrs.NewDefaultProvider(provider.Name)
}
if testProvider, exists := test.Providers[provider.Addr().StringCompact()]; exists {
if testProvider, exists := test.Providers[provider.moduleUniqueKey()]; exists {
testProviderType := testProvider.providerType
if testProviderType.IsZero() {
testProviderType = addrs.NewDefaultProvider(testProvider.Name)
@ -141,7 +142,7 @@ func validateProviderConfigsForTests(cfg *Config) (diags hcl.Diagnostics) {
Summary: "Provider type mismatch",
Detail: fmt.Sprintf(
"The provider %q in %s represents provider %q, but %q in the root module represents %q.\n\nThis means the provider definition for %q within %s has been referenced by multiple run blocks and assigned to different provider types.",
testProvider.Addr().StringCompact(), name, testProviderType, provider.Addr().StringCompact(), providerType, testProvider.Addr().StringCompact(), name),
testProvider.moduleUniqueKey(), name, testProviderType, provider.moduleUniqueKey(), providerType, testProvider.moduleUniqueKey(), name),
Subject: testProvider.DeclRange.Ptr(),
})
}
@ -177,6 +178,7 @@ func validateProviderConfigsForTests(cfg *Config) (diags hcl.Diagnostics) {
Name: provider.Name,
NameRange: provider.NameRange,
Alias: provider.Alias,
AliasRange: provider.AliasRange,
providerType: provider.providerType,
},
}
@ -201,6 +203,7 @@ func validateProviderConfigsForTests(cfg *Config) (diags hcl.Diagnostics) {
Name: provider.Name,
NameRange: provider.NameRange,
Alias: provider.Alias,
AliasRange: provider.AliasRange,
providerType: provider.providerType,
},
}
@ -220,7 +223,7 @@ func validateProviderConfigsForTests(cfg *Config) (diags hcl.Diagnostics) {
// better error messages to use these.
for _, provider := range cfg.Module.ProviderConfigs {
key := provider.Addr().StringCompact()
key := provider.moduleUniqueKey()
if testProvider, exists := test.Providers[key]; exists {
matchedProviders[key] = PassedProviderConfig{
@ -235,6 +238,7 @@ func validateProviderConfigsForTests(cfg *Config) (diags hcl.Diagnostics) {
Name: testProvider.Name,
NameRange: testProvider.NameRange,
Alias: testProvider.Alias,
AliasRange: testProvider.AliasRange,
providerType: testProvider.providerType,
},
}

View File

@ -100,14 +100,13 @@ func (file *TestFile) getTestProviderOrMock(addr string) (*Provider, bool) {
mockProvider, ok := file.MockProviders[addr]
if ok {
p := &Provider{
ProviderCommon: ProviderCommon{
Name: mockProvider.Name,
NameRange: mockProvider.NameRange,
DeclRange: mockProvider.DeclRange,
IsMocked: true,
MockResources: mockProvider.MockResources,
},
Alias: mockProvider.Alias,
Name: mockProvider.Name,
NameRange: mockProvider.NameRange,
Alias: mockProvider.Alias,
AliasRange: mockProvider.AliasRange,
DeclRange: mockProvider.DeclRange,
IsMocked: true,
MockResources: mockProvider.MockResources,
}
return p, true
@ -397,17 +396,10 @@ func loadTestFile(body hcl.Body) (*TestFile, hcl.Diagnostics) {
}
case "provider":
providerBlock, providerDiags := decodeProviderBlock(block)
provider, providerDiags := decodeProviderBlock(block)
diags = append(diags, providerDiags...)
if providerBlock != nil {
providers, diagsStatic := providerBlock.decodeStaticFields(nil)
diags = append(diags, diagsStatic...)
if diagsStatic.HasErrors() {
continue
}
for _, provider := range providers {
tf.Providers[provider.Addr().StringCompact()] = provider
}
if provider != nil {
tf.Providers[provider.moduleUniqueKey()] = provider
}
case blockNameOverrideResource:

View File

@ -1,28 +0,0 @@
terraform {
required_providers {
local = {
source = "hashicorp/local"
}
}
}
locals {
files = {
dev = {
filename = "/tmp/dev"
content = "who dis?"
}
test = {
filename = "/tmp/test"
content = "testing 1 2 3"
}
prod = {
filename = "/tmp/prod"
content = "this is a serious string, because it's production"
}
}
}
provider "foo-test" {
for_each = local.files
}

View File

@ -1,28 +0,0 @@
terraform {
required_providers {
local = {
source = "hashicorp/local"
}
}
}
locals {
files = {
0 = {
filename = "/tmp/0"
content = "who dis?"
}
test = {
filename = "/tmp/test"
content = "testing 1 2 3"
}
prod = {
filename = "/tmp/prod"
content = "this is a serious string, because it's production"
}
}
}
provider "foo-test" {
for_each = local.files
}

View File

@ -11,7 +11,3 @@ provider "bar" {
alias = "bar"
}
provider "baz" {
for_each = {"a": "first", "b": "second"}
}

View File

@ -34,10 +34,8 @@ func TestBuildProviderConfig(t *testing.T) {
},
}
gotBody := buildProviderConfig(ctx, providerAddr, &configs.Provider{
ProviderCommon: configs.ProviderCommon{
Name: "foo",
Config: configBody,
},
Name: "foo",
Config: configBody,
})
schema := &configschema.Block{

View File

@ -79,9 +79,6 @@ func (n *NodeApplyableProvider) ValidateProvider(ctx EvalContext, provider provi
}
data := EvalDataForNoInstanceKey
if n.Config != nil {
data = n.Config.InstanceData
}
configVal, _, evalDiags := ctx.EvaluateBlock(configBody, configSchema, nil, data)
if evalDiags.HasErrors() {
@ -119,9 +116,6 @@ func (n *NodeApplyableProvider) ConfigureProvider(ctx EvalContext, provider prov
configSchema := resp.Provider.Block
data := EvalDataForNoInstanceKey
if n.Config != nil {
data = n.Config.InstanceData
}
configVal, configBody, evalDiags := ctx.EvaluateBlock(configBody, configSchema, nil, data)
diags = diags.Append(evalDiags)

View File

@ -22,12 +22,10 @@ import (
func TestNodeApplyableProviderExecute(t *testing.T) {
config := &configs.Provider{
ProviderCommon: configs.ProviderCommon{
Name: "foo",
Config: configs.SynthBody("", map[string]cty.Value{
"user": cty.StringVal("hello"),
}),
},
Name: "foo",
Config: configs.SynthBody("", map[string]cty.Value{
"user": cty.StringVal("hello"),
}),
}
schema := &configschema.Block{
@ -85,12 +83,10 @@ func TestNodeApplyableProviderExecute(t *testing.T) {
func TestNodeApplyableProviderExecute_unknownImport(t *testing.T) {
config := &configs.Provider{
ProviderCommon: configs.ProviderCommon{
Name: "foo",
Config: configs.SynthBody("", map[string]cty.Value{
"test_string": cty.UnknownVal(cty.String),
}),
},
Name: "foo",
Config: configs.SynthBody("", map[string]cty.Value{
"test_string": cty.UnknownVal(cty.String),
}),
}
provider := mockProviderWithConfigSchema(simpleTestSchema())
providerAddr := addrs.AbsProviderConfig{
@ -122,12 +118,10 @@ func TestNodeApplyableProviderExecute_unknownImport(t *testing.T) {
func TestNodeApplyableProviderExecute_unknownApply(t *testing.T) {
config := &configs.Provider{
ProviderCommon: configs.ProviderCommon{
Name: "foo",
Config: configs.SynthBody("", map[string]cty.Value{
"test_string": cty.UnknownVal(cty.String),
}),
},
Name: "foo",
Config: configs.SynthBody("", map[string]cty.Value{
"test_string": cty.UnknownVal(cty.String),
}),
}
provider := mockProviderWithConfigSchema(simpleTestSchema())
providerAddr := addrs.AbsProviderConfig{
@ -160,12 +154,10 @@ func TestNodeApplyableProviderExecute_unknownApply(t *testing.T) {
func TestNodeApplyableProviderExecute_sensitive(t *testing.T) {
config := &configs.Provider{
ProviderCommon: configs.ProviderCommon{
Name: "foo",
Config: configs.SynthBody("", map[string]cty.Value{
"test_string": cty.StringVal("hello").Mark(marks.Sensitive),
}),
},
Name: "foo",
Config: configs.SynthBody("", map[string]cty.Value{
"test_string": cty.StringVal("hello").Mark(marks.Sensitive),
}),
}
provider := mockProviderWithConfigSchema(simpleTestSchema())
providerAddr := addrs.AbsProviderConfig{
@ -199,12 +191,10 @@ func TestNodeApplyableProviderExecute_sensitive(t *testing.T) {
func TestNodeApplyableProviderExecute_sensitiveValidate(t *testing.T) {
config := &configs.Provider{
ProviderCommon: configs.ProviderCommon{
Name: "foo",
Config: configs.SynthBody("", map[string]cty.Value{
"test_string": cty.StringVal("hello").Mark(marks.Sensitive),
}),
},
Name: "foo",
Config: configs.SynthBody("", map[string]cty.Value{
"test_string": cty.StringVal("hello").Mark(marks.Sensitive),
}),
}
provider := mockProviderWithConfigSchema(simpleTestSchema())
providerAddr := addrs.AbsProviderConfig{
@ -238,10 +228,8 @@ func TestNodeApplyableProviderExecute_sensitiveValidate(t *testing.T) {
func TestNodeApplyableProviderExecute_emptyValidate(t *testing.T) {
config := &configs.Provider{
ProviderCommon: configs.ProviderCommon{
Name: "foo",
Config: configs.SynthBody("", map[string]cty.Value{}),
},
Name: "foo",
Config: configs.SynthBody("", map[string]cty.Value{}),
}
provider := mockProviderWithConfigSchema(&configschema.Block{
Attributes: map[string]*configschema.Attribute{
@ -286,12 +274,10 @@ func TestNodeApplyableProvider_Validate(t *testing.T) {
t.Run("valid", func(t *testing.T) {
config := &configs.Provider{
ProviderCommon: configs.ProviderCommon{
Name: "test",
Config: configs.SynthBody("", map[string]cty.Value{
"region": cty.StringVal("mars"),
}),
},
Name: "test",
Config: configs.SynthBody("", map[string]cty.Value{
"region": cty.StringVal("mars"),
}),
}
node := NodeApplyableProvider{
@ -309,12 +295,10 @@ func TestNodeApplyableProvider_Validate(t *testing.T) {
t.Run("invalid", func(t *testing.T) {
config := &configs.Provider{
ProviderCommon: configs.ProviderCommon{
Name: "test",
Config: configs.SynthBody("", map[string]cty.Value{
"region": cty.MapValEmpty(cty.String),
}),
},
Name: "test",
Config: configs.SynthBody("", map[string]cty.Value{
"region": cty.MapValEmpty(cty.String),
}),
}
node := NodeApplyableProvider{
@ -372,12 +356,10 @@ func TestNodeApplyableProvider_ConfigProvider(t *testing.T) {
t.Run("valid", func(t *testing.T) {
config := &configs.Provider{
ProviderCommon: configs.ProviderCommon{
Name: "test",
Config: configs.SynthBody("", map[string]cty.Value{
"region": cty.StringVal("mars"),
}),
},
Name: "test",
Config: configs.SynthBody("", map[string]cty.Value{
"region": cty.StringVal("mars"),
}),
}
node := NodeApplyableProvider{
@ -411,10 +393,8 @@ func TestNodeApplyableProvider_ConfigProvider(t *testing.T) {
t.Run("missing required config", func(t *testing.T) {
config := &configs.Provider{
ProviderCommon: configs.ProviderCommon{
Name: "test",
Config: hcl.EmptyBody(),
},
Name: "test",
Config: hcl.EmptyBody(),
}
node := NodeApplyableProvider{
NodeAbstractProvider: &NodeAbstractProvider{
@ -465,12 +445,10 @@ func TestNodeApplyableProvider_ConfigProvider_config_fn_err(t *testing.T) {
t.Run("valid", func(t *testing.T) {
config := &configs.Provider{
ProviderCommon: configs.ProviderCommon{
Name: "test",
Config: configs.SynthBody("", map[string]cty.Value{
"region": cty.StringVal("mars"),
}),
},
Name: "test",
Config: configs.SynthBody("", map[string]cty.Value{
"region": cty.StringVal("mars"),
}),
}
node := NodeApplyableProvider{
@ -504,10 +482,8 @@ func TestNodeApplyableProvider_ConfigProvider_config_fn_err(t *testing.T) {
t.Run("missing required config", func(t *testing.T) {
config := &configs.Provider{
ProviderCommon: configs.ProviderCommon{
Name: "test",
Config: hcl.EmptyBody(),
},
Name: "test",
Config: hcl.EmptyBody(),
}
node := NodeApplyableProvider{
NodeAbstractProvider: &NodeAbstractProvider{