opentofu/internal/configs/config_test.go
Christian Mesh e802b23200
Partial revert of #1911, support provider for_each in aliases (#2121)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
2024-11-05 06:19:52 -05:00

833 lines
29 KiB
Go

// 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 (
"bytes"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"github.com/go-test/deep"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclparse"
"github.com/zclconf/go-cty/cty"
version "github.com/hashicorp/go-version"
"github.com/hashicorp/hcl/v2/hclsyntax"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/depsfile"
"github.com/opentofu/opentofu/internal/getproviders"
)
func TestConfigProviderTypes(t *testing.T) {
// nil cfg should return an empty map
got := NewEmptyConfig().ProviderTypes()
if len(got) != 0 {
t.Fatal("expected empty result from empty config")
}
cfg, diags := testModuleConfigFromFile("testdata/valid-files/providers-explicit-implied.tf")
if diags.HasErrors() {
t.Fatal(diags.Error())
}
got = cfg.ProviderTypes()
want := []addrs.Provider{
addrs.NewDefaultProvider("aws"),
addrs.NewDefaultProvider("local"),
addrs.NewDefaultProvider("null"),
addrs.NewDefaultProvider("template"),
addrs.NewDefaultProvider("test"),
}
for _, problem := range deep.Equal(got, want) {
t.Error(problem)
}
}
func TestConfigProviderTypes_nested(t *testing.T) {
// basic test with a nil config
c := NewEmptyConfig()
got := c.ProviderTypes()
if len(got) != 0 {
t.Fatalf("wrong result!\ngot: %#v\nwant: nil\n", got)
}
// config with two provider sources, and one implicit (default) provider
cfg, diags := testNestedModuleConfigFromDir(t, "testdata/valid-modules/nested-providers-fqns")
if diags.HasErrors() {
t.Fatal(diags.Error())
}
got = cfg.ProviderTypes()
want := []addrs.Provider{
addrs.NewProvider(addrs.DefaultProviderRegistryHost, "bar", "test"),
addrs.NewProvider(addrs.DefaultProviderRegistryHost, "foo", "test"),
addrs.NewDefaultProvider("test"),
}
for _, problem := range deep.Equal(got, want) {
t.Error(problem)
}
}
func TestConfigResolveAbsProviderAddr(t *testing.T) {
cfg, diags := testModuleConfigFromDir("testdata/providers-explicit-fqn")
if diags.HasErrors() {
t.Fatal(diags.Error())
}
t.Run("already absolute", func(t *testing.T) {
addr := addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("test"),
Alias: "boop",
}
got := cfg.ResolveAbsProviderAddr(addr, addrs.RootModule)
if got, want := got.String(), addr.String(); got != want {
t.Errorf("wrong result\ngot: %s\nwant: %s", got, want)
}
})
t.Run("local, implied mapping", func(t *testing.T) {
addr := addrs.LocalProviderConfig{
LocalName: "implied",
Alias: "boop",
}
got := cfg.ResolveAbsProviderAddr(addr, addrs.RootModule)
want := addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("implied"),
Alias: "boop",
}
if got, want := got.String(), want.String(); got != want {
t.Errorf("wrong result\ngot: %s\nwant: %s", got, want)
}
})
t.Run("local, explicit mapping", func(t *testing.T) {
addr := addrs.LocalProviderConfig{
LocalName: "foo-test", // this is explicitly set in the config
Alias: "boop",
}
got := cfg.ResolveAbsProviderAddr(addr, addrs.RootModule)
want := addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewProvider(addrs.DefaultProviderRegistryHost, "foo", "test"),
Alias: "boop",
}
if got, want := got.String(), want.String(); got != want {
t.Errorf("wrong result\ngot: %s\nwant: %s", got, want)
}
})
}
func TestConfigProviderRequirements(t *testing.T) {
cfg, diags := testNestedModuleConfigFromDir(t, "testdata/provider-reqs")
// TODO: Version Constraint Deprecation.
// Once we've removed the version argument from provider configuration
// blocks, this can go back to expected 0 diagnostics.
// assertNoDiagnostics(t, diags)
assertDiagnosticCount(t, diags, 1)
assertDiagnosticSummary(t, diags, "Version constraints inside provider configuration blocks are deprecated")
tlsProvider := addrs.NewProvider(
addrs.DefaultProviderRegistryHost,
"hashicorp", "tls",
)
happycloudProvider := addrs.NewProvider(
svchost.Hostname("tf.example.com"),
"awesomecorp", "happycloud",
)
nullProvider := addrs.NewDefaultProvider("null")
randomProvider := addrs.NewDefaultProvider("random")
impliedProvider := addrs.NewDefaultProvider("implied")
terraformProvider := addrs.NewBuiltInProvider("terraform")
configuredProvider := addrs.NewDefaultProvider("configured")
grandchildProvider := addrs.NewDefaultProvider("grandchild")
got, diags := cfg.ProviderRequirements()
assertNoDiagnostics(t, diags)
want := getproviders.Requirements{
// the nullProvider constraints from the two modules are merged
nullProvider: getproviders.MustParseVersionConstraints("~> 2.0.0, 2.0.1"),
randomProvider: getproviders.MustParseVersionConstraints("~> 1.2.0"),
tlsProvider: getproviders.MustParseVersionConstraints("~> 3.0"),
configuredProvider: getproviders.MustParseVersionConstraints("~> 1.4"),
impliedProvider: nil,
happycloudProvider: nil,
terraformProvider: nil,
grandchildProvider: nil,
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong result\n%s", diff)
}
}
func TestConfigProviderRequirementsInclTests(t *testing.T) {
cfg, diags := testNestedModuleConfigFromDirWithTests(t, "testdata/provider-reqs-with-tests")
// TODO: Version Constraint Deprecation.
// Once we've removed the version argument from provider configuration
// blocks, this can go back to expected 0 diagnostics.
// assertNoDiagnostics(t, diags)
assertDiagnosticCount(t, diags, 1)
assertDiagnosticSummary(t, diags, "Version constraints inside provider configuration blocks are deprecated")
tlsProvider := addrs.NewProvider(
addrs.DefaultProviderRegistryHost,
"hashicorp", "tls",
)
nullProvider := addrs.NewDefaultProvider("null")
randomProvider := addrs.NewDefaultProvider("random")
impliedProvider := addrs.NewDefaultProvider("implied")
terraformProvider := addrs.NewBuiltInProvider("terraform")
configuredProvider := addrs.NewDefaultProvider("configured")
got, diags := cfg.ProviderRequirements()
assertNoDiagnostics(t, diags)
want := getproviders.Requirements{
// the nullProvider constraints from the two modules are merged
nullProvider: getproviders.MustParseVersionConstraints("~> 2.0.0"),
randomProvider: getproviders.MustParseVersionConstraints("~> 1.2.0"),
tlsProvider: getproviders.MustParseVersionConstraints("~> 3.0"),
configuredProvider: getproviders.MustParseVersionConstraints("~> 1.4"),
impliedProvider: nil,
terraformProvider: nil,
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong result\n%s", diff)
}
}
func TestConfigProviderRequirementsDuplicate(t *testing.T) {
_, diags := testNestedModuleConfigFromDir(t, "testdata/duplicate-local-name")
assertDiagnosticCount(t, diags, 3)
assertDiagnosticSummary(t, diags, "Duplicate required provider")
}
func TestConfigProviderRequirementsShallow(t *testing.T) {
cfg, diags := testNestedModuleConfigFromDir(t, "testdata/provider-reqs")
// TODO: Version Constraint Deprecation.
// Once we've removed the version argument from provider configuration
// blocks, this can go back to expected 0 diagnostics.
// assertNoDiagnostics(t, diags)
assertDiagnosticCount(t, diags, 1)
assertDiagnosticSummary(t, diags, "Version constraints inside provider configuration blocks are deprecated")
tlsProvider := addrs.NewProvider(
addrs.DefaultProviderRegistryHost,
"hashicorp", "tls",
)
nullProvider := addrs.NewDefaultProvider("null")
randomProvider := addrs.NewDefaultProvider("random")
impliedProvider := addrs.NewDefaultProvider("implied")
terraformProvider := addrs.NewBuiltInProvider("terraform")
configuredProvider := addrs.NewDefaultProvider("configured")
got, diags := cfg.ProviderRequirementsShallow()
assertNoDiagnostics(t, diags)
want := getproviders.Requirements{
// the nullProvider constraint is only from the root module
nullProvider: getproviders.MustParseVersionConstraints("~> 2.0.0"),
randomProvider: getproviders.MustParseVersionConstraints("~> 1.2.0"),
tlsProvider: getproviders.MustParseVersionConstraints("~> 3.0"),
configuredProvider: getproviders.MustParseVersionConstraints("~> 1.4"),
impliedProvider: nil,
terraformProvider: nil,
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong result\n%s", diff)
}
}
func TestConfigProviderRequirementsShallowInclTests(t *testing.T) {
cfg, diags := testNestedModuleConfigFromDirWithTests(t, "testdata/provider-reqs-with-tests")
// TODO: Version Constraint Deprecation.
// Once we've removed the version argument from provider configuration
// blocks, this can go back to expected 0 diagnostics.
// assertNoDiagnostics(t, diags)
assertDiagnosticCount(t, diags, 1)
assertDiagnosticSummary(t, diags, "Version constraints inside provider configuration blocks are deprecated")
tlsProvider := addrs.NewProvider(
addrs.DefaultProviderRegistryHost,
"hashicorp", "tls",
)
impliedProvider := addrs.NewDefaultProvider("implied")
terraformProvider := addrs.NewBuiltInProvider("terraform")
configuredProvider := addrs.NewDefaultProvider("configured")
got, diags := cfg.ProviderRequirementsShallow()
assertNoDiagnostics(t, diags)
want := getproviders.Requirements{
tlsProvider: getproviders.MustParseVersionConstraints("~> 3.0"),
configuredProvider: getproviders.MustParseVersionConstraints("~> 1.4"),
impliedProvider: nil,
terraformProvider: nil,
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong result\n%s", diff)
}
}
func TestConfigProviderRequirementsByModule(t *testing.T) {
cfg, diags := testNestedModuleConfigFromDir(t, "testdata/provider-reqs")
// TODO: Version Constraint Deprecation.
// Once we've removed the version argument from provider configuration
// blocks, this can go back to expected 0 diagnostics.
// assertNoDiagnostics(t, diags)
assertDiagnosticCount(t, diags, 1)
assertDiagnosticSummary(t, diags, "Version constraints inside provider configuration blocks are deprecated")
tlsProvider := addrs.NewProvider(
addrs.DefaultProviderRegistryHost,
"hashicorp", "tls",
)
happycloudProvider := addrs.NewProvider(
svchost.Hostname("tf.example.com"),
"awesomecorp", "happycloud",
)
nullProvider := addrs.NewDefaultProvider("null")
randomProvider := addrs.NewDefaultProvider("random")
impliedProvider := addrs.NewDefaultProvider("implied")
terraformProvider := addrs.NewBuiltInProvider("terraform")
configuredProvider := addrs.NewDefaultProvider("configured")
grandchildProvider := addrs.NewDefaultProvider("grandchild")
got, diags := cfg.ProviderRequirementsByModule()
assertNoDiagnostics(t, diags)
want := &ModuleRequirements{
Name: "",
SourceAddr: nil,
SourceDir: "testdata/provider-reqs",
Requirements: getproviders.Requirements{
// Only the root module's version is present here
nullProvider: getproviders.MustParseVersionConstraints("~> 2.0.0"),
randomProvider: getproviders.MustParseVersionConstraints("~> 1.2.0"),
tlsProvider: getproviders.MustParseVersionConstraints("~> 3.0"),
configuredProvider: getproviders.MustParseVersionConstraints("~> 1.4"),
impliedProvider: nil,
terraformProvider: nil,
},
Children: map[string]*ModuleRequirements{
"kinder": {
Name: "kinder",
SourceAddr: addrs.ModuleSourceLocal("./child"),
SourceDir: filepath.FromSlash("testdata/provider-reqs/child"),
Requirements: getproviders.Requirements{
nullProvider: getproviders.MustParseVersionConstraints("= 2.0.1"),
happycloudProvider: nil,
},
Children: map[string]*ModuleRequirements{
"nested": {
Name: "nested",
SourceAddr: addrs.ModuleSourceLocal("./grandchild"),
SourceDir: filepath.FromSlash("testdata/provider-reqs/child/grandchild"),
Requirements: getproviders.Requirements{
grandchildProvider: nil,
},
Children: map[string]*ModuleRequirements{},
Tests: make(map[string]*TestFileModuleRequirements),
},
},
Tests: make(map[string]*TestFileModuleRequirements),
},
},
Tests: make(map[string]*TestFileModuleRequirements),
}
ignore := cmpopts.IgnoreUnexported(version.Constraint{}, cty.Value{}, hclsyntax.Body{})
if diff := cmp.Diff(want, got, ignore); diff != "" {
t.Errorf("wrong result\n%s", diff)
}
}
func TestConfigProviderRequirementsByModuleInclTests(t *testing.T) {
cfg, diags := testNestedModuleConfigFromDirWithTests(t, "testdata/provider-reqs-with-tests")
// TODO: Version Constraint Deprecation.
// Once we've removed the version argument from provider configuration
// blocks, this can go back to expected 0 diagnostics.
// assertNoDiagnostics(t, diags)
assertDiagnosticCount(t, diags, 1)
assertDiagnosticSummary(t, diags, "Version constraints inside provider configuration blocks are deprecated")
tlsProvider := addrs.NewProvider(
addrs.DefaultProviderRegistryHost,
"hashicorp", "tls",
)
nullProvider := addrs.NewDefaultProvider("null")
randomProvider := addrs.NewDefaultProvider("random")
impliedProvider := addrs.NewDefaultProvider("implied")
terraformProvider := addrs.NewBuiltInProvider("terraform")
configuredProvider := addrs.NewDefaultProvider("configured")
got, diags := cfg.ProviderRequirementsByModule()
assertNoDiagnostics(t, diags)
want := &ModuleRequirements{
Name: "",
SourceAddr: nil,
SourceDir: "testdata/provider-reqs-with-tests",
Requirements: getproviders.Requirements{
// Only the root module's version is present here
tlsProvider: getproviders.MustParseVersionConstraints("~> 3.0"),
impliedProvider: nil,
terraformProvider: nil,
},
Children: make(map[string]*ModuleRequirements),
Tests: map[string]*TestFileModuleRequirements{
"provider-reqs-root.tftest.hcl": {
Requirements: getproviders.Requirements{
configuredProvider: getproviders.MustParseVersionConstraints("~> 1.4"),
},
Runs: map[string]*ModuleRequirements{
"setup": {
Name: "setup",
SourceAddr: addrs.ModuleSourceLocal("./setup"),
SourceDir: filepath.FromSlash("testdata/provider-reqs-with-tests/setup"),
Requirements: getproviders.Requirements{
nullProvider: getproviders.MustParseVersionConstraints("~> 2.0.0"),
randomProvider: getproviders.MustParseVersionConstraints("~> 1.2.0"),
},
Children: make(map[string]*ModuleRequirements),
Tests: make(map[string]*TestFileModuleRequirements),
},
},
},
},
}
ignore := cmpopts.IgnoreUnexported(version.Constraint{}, cty.Value{}, hclsyntax.Body{})
if diff := cmp.Diff(want, got, ignore); diff != "" {
t.Errorf("wrong result\n%s", diff)
}
}
func TestVerifyDependencySelections(t *testing.T) {
cfg, diags := testNestedModuleConfigFromDir(t, "testdata/provider-reqs")
// TODO: Version Constraint Deprecation.
// Once we've removed the version argument from provider configuration
// blocks, this can go back to expected 0 diagnostics.
// assertNoDiagnostics(t, diags)
assertDiagnosticCount(t, diags, 1)
assertDiagnosticSummary(t, diags, "Version constraints inside provider configuration blocks are deprecated")
tlsProvider := addrs.NewProvider(
addrs.DefaultProviderRegistryHost,
"hashicorp", "tls",
)
happycloudProvider := addrs.NewProvider(
svchost.Hostname("tf.example.com"),
"awesomecorp", "happycloud",
)
nullProvider := addrs.NewDefaultProvider("null")
randomProvider := addrs.NewDefaultProvider("random")
impliedProvider := addrs.NewDefaultProvider("implied")
configuredProvider := addrs.NewDefaultProvider("configured")
grandchildProvider := addrs.NewDefaultProvider("grandchild")
tests := map[string]struct {
PrepareLocks func(*depsfile.Locks)
WantErrs []string
}{
"empty locks": {
func(*depsfile.Locks) {
// Intentionally blank
},
[]string{
`provider registry.opentofu.org/hashicorp/configured: required by this configuration but no version is selected`,
`provider registry.opentofu.org/hashicorp/grandchild: required by this configuration but no version is selected`,
`provider registry.opentofu.org/hashicorp/implied: required by this configuration but no version is selected`,
`provider registry.opentofu.org/hashicorp/null: required by this configuration but no version is selected`,
`provider registry.opentofu.org/hashicorp/random: required by this configuration but no version is selected`,
`provider registry.opentofu.org/hashicorp/tls: required by this configuration but no version is selected`,
`provider tf.example.com/awesomecorp/happycloud: required by this configuration but no version is selected`,
},
},
"suitable locks": {
func(locks *depsfile.Locks) {
locks.SetProvider(configuredProvider, getproviders.MustParseVersion("1.4.0"), nil, nil)
locks.SetProvider(grandchildProvider, getproviders.MustParseVersion("0.1.0"), nil, nil)
locks.SetProvider(impliedProvider, getproviders.MustParseVersion("0.2.0"), nil, nil)
locks.SetProvider(nullProvider, getproviders.MustParseVersion("2.0.1"), nil, nil)
locks.SetProvider(randomProvider, getproviders.MustParseVersion("1.2.2"), nil, nil)
locks.SetProvider(tlsProvider, getproviders.MustParseVersion("3.0.1"), nil, nil)
locks.SetProvider(happycloudProvider, getproviders.MustParseVersion("0.0.1"), nil, nil)
},
nil,
},
"null provider constraints changed": {
func(locks *depsfile.Locks) {
locks.SetProvider(configuredProvider, getproviders.MustParseVersion("1.4.0"), nil, nil)
locks.SetProvider(grandchildProvider, getproviders.MustParseVersion("0.1.0"), nil, nil)
locks.SetProvider(impliedProvider, getproviders.MustParseVersion("0.2.0"), nil, nil)
locks.SetProvider(nullProvider, getproviders.MustParseVersion("3.0.0"), nil, nil)
locks.SetProvider(randomProvider, getproviders.MustParseVersion("1.2.2"), nil, nil)
locks.SetProvider(tlsProvider, getproviders.MustParseVersion("3.0.1"), nil, nil)
locks.SetProvider(happycloudProvider, getproviders.MustParseVersion("0.0.1"), nil, nil)
},
[]string{
`provider registry.opentofu.org/hashicorp/null: locked version selection 3.0.0 doesn't match the updated version constraints "~> 2.0.0, 2.0.1"`,
},
},
"null provider lock changed": {
func(locks *depsfile.Locks) {
// In this case, we set the lock file version constraints to
// match the configuration, and so our error message changes
// to not assume the configuration changed anymore.
locks.SetProvider(nullProvider, getproviders.MustParseVersion("3.0.0"), getproviders.MustParseVersionConstraints("~> 2.0.0, 2.0.1"), nil)
locks.SetProvider(configuredProvider, getproviders.MustParseVersion("1.4.0"), nil, nil)
locks.SetProvider(grandchildProvider, getproviders.MustParseVersion("0.1.0"), nil, nil)
locks.SetProvider(impliedProvider, getproviders.MustParseVersion("0.2.0"), nil, nil)
locks.SetProvider(randomProvider, getproviders.MustParseVersion("1.2.2"), nil, nil)
locks.SetProvider(tlsProvider, getproviders.MustParseVersion("3.0.1"), nil, nil)
locks.SetProvider(happycloudProvider, getproviders.MustParseVersion("0.0.1"), nil, nil)
},
[]string{
`provider registry.opentofu.org/hashicorp/null: version constraints "~> 2.0.0, 2.0.1" don't match the locked version selection 3.0.0`,
},
},
"overridden provider": {
func(locks *depsfile.Locks) {
locks.SetProviderOverridden(happycloudProvider)
},
[]string{
// We still catch all of the other ones, because only happycloud was overridden
`provider registry.opentofu.org/hashicorp/configured: required by this configuration but no version is selected`,
`provider registry.opentofu.org/hashicorp/grandchild: required by this configuration but no version is selected`,
`provider registry.opentofu.org/hashicorp/implied: required by this configuration but no version is selected`,
`provider registry.opentofu.org/hashicorp/null: required by this configuration but no version is selected`,
`provider registry.opentofu.org/hashicorp/random: required by this configuration but no version is selected`,
`provider registry.opentofu.org/hashicorp/tls: required by this configuration but no version is selected`,
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
depLocks := depsfile.NewLocks()
test.PrepareLocks(depLocks)
gotErrs := cfg.VerifyDependencySelections(depLocks)
var gotErrsStr []string
if gotErrs != nil {
gotErrsStr = make([]string, len(gotErrs))
for i, err := range gotErrs {
gotErrsStr[i] = err.Error()
}
}
if diff := cmp.Diff(test.WantErrs, gotErrsStr); diff != "" {
t.Errorf("wrong errors\n%s", diff)
}
})
}
}
func TestConfigProviderForConfigAddr(t *testing.T) {
cfg, diags := testModuleConfigFromDir("testdata/valid-modules/providers-fqns")
assertNoDiagnostics(t, diags)
got := cfg.ProviderForConfigAddr(addrs.NewDefaultLocalProviderConfig("foo-test"))
want := addrs.NewProvider(addrs.DefaultProviderRegistryHost, "foo", "test")
if !got.Equals(want) {
t.Errorf("wrong result\ngot: %s\nwant: %s", got, want)
}
// now check a provider that isn't in the configuration. It should return a DefaultProvider.
got = cfg.ProviderForConfigAddr(addrs.NewDefaultLocalProviderConfig("bar-test"))
want = addrs.NewDefaultProvider("bar-test")
if !got.Equals(want) {
t.Errorf("wrong result\ngot: %s\nwant: %s", got, want)
}
}
func TestConfigAddProviderRequirements(t *testing.T) {
cfg, diags := testModuleConfigFromFile("testdata/valid-files/providers-explicit-implied.tf")
assertNoDiagnostics(t, diags)
reqs := getproviders.Requirements{
addrs.NewDefaultProvider("null"): nil,
}
diags = cfg.addProviderRequirements(reqs, true, false)
assertNoDiagnostics(t, diags)
}
func TestConfigImportProviderClashesWithModules(t *testing.T) {
src, err := os.ReadFile("testdata/invalid-import-files/import-and-module-clash.tf")
if err != nil {
t.Fatal(err)
}
parser := testParser(map[string]string{
"main.tf": string(src),
})
_, diags := parser.LoadConfigFile("main.tf")
assertExactDiagnostics(t, diags, []string{
`main.tf:9,3-19: Invalid import provider argument; The provider argument can only be specified in import blocks that will generate configuration.
Use the providers argument within the module block to configure providers for all resources within a module, including imported resources.`,
})
}
func TestConfigImportProviderClashesWithResources(t *testing.T) {
cfg, diags := testModuleConfigFromFile("testdata/invalid-import-files/import-and-resource-clash.tf")
assertNoDiagnostics(t, diags)
diags = cfg.addProviderRequirements(getproviders.Requirements{}, true, false)
assertExactDiagnostics(t, diags, []string{
`testdata/invalid-import-files/import-and-resource-clash.tf:9,3-19: Invalid import provider argument; The provider argument can only be specified in import blocks that will generate configuration.
Use the provider argument in the target resource block to configure the provider for a resource with explicit provider configuration.`,
})
}
func TestConfigImportProviderWithNoResourceProvider(t *testing.T) {
cfg, diags := testModuleConfigFromFile("testdata/invalid-import-files/import-and-no-resource.tf")
assertNoDiagnostics(t, diags)
diags = cfg.addProviderRequirements(getproviders.Requirements{}, true, false)
assertExactDiagnostics(t, diags, []string{
`testdata/invalid-import-files/import-and-no-resource.tf:5,3-19: Invalid import provider argument; The provider argument can only be specified in import blocks that will generate configuration.
Use the provider argument in the target resource block to configure the provider for a resource with explicit provider configuration.`,
})
}
func TestTransformForTest(t *testing.T) {
str := func(providers map[string]string) string {
var buffer bytes.Buffer
for key, config := range providers {
buffer.WriteString(fmt.Sprintf("%s: %s\n", key, config))
}
return buffer.String()
}
convertToProviders := func(t *testing.T, contents map[string]string) map[string]*Provider {
t.Helper()
providers := make(map[string]*Provider)
for key, content := range contents {
parser := hclparse.NewParser()
file, diags := parser.ParseHCL([]byte(content), fmt.Sprintf("%s.hcl", key))
if diags.HasErrors() {
t.Fatal(diags.Error())
}
provider := &Provider{
Config: file.Body,
}
parts := strings.Split(key, ".")
provider.Name = parts[0]
if len(parts) > 1 {
provider.Alias = parts[1]
}
providers[key] = provider
}
return providers
}
validate := func(t *testing.T, msg string, expected map[string]string, actual map[string]*Provider) {
t.Helper()
converted := make(map[string]string)
for key, provider := range actual {
content, err := provider.Config.Content(&hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: "source", Required: true},
},
})
if err != nil {
t.Fatal(err)
}
source, diags := content.Attributes["source"].Expr.Value(nil)
if diags.HasErrors() {
t.Fatal(diags.Error())
}
converted[key] = fmt.Sprintf("source = %q", source.AsString())
}
if diff := cmp.Diff(expected, converted); len(diff) > 0 {
t.Errorf("%s\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", msg, str(expected), str(converted), diff)
}
}
tcs := map[string]struct {
configProviders map[string]string
fileProviders map[string]string
runProviders []PassedProviderConfig
expectedProviders map[string]string
expectedErrors []string
}{
"empty": {
configProviders: make(map[string]string),
expectedProviders: make(map[string]string),
},
"only providers in config": {
configProviders: map[string]string{
"foo": "source = \"config\"",
"bar": "source = \"config\"",
},
expectedProviders: map[string]string{
"foo": "source = \"config\"",
"bar": "source = \"config\"",
},
},
"only providers in test file": {
configProviders: make(map[string]string),
fileProviders: map[string]string{
"foo": "source = \"testfile\"",
"bar": "source = \"testfile\"",
},
expectedProviders: map[string]string{
"foo": "source = \"testfile\"",
"bar": "source = \"testfile\"",
},
},
"only providers in run block": {
configProviders: make(map[string]string),
runProviders: []PassedProviderConfig{
{
InChild: &ProviderConfigRef{
Name: "foo",
},
InParent: &ProviderConfigRef{
Name: "bar",
},
},
},
expectedProviders: make(map[string]string),
expectedErrors: []string{
":0,0-0: Missing provider definition for bar; This provider block references a provider definition that does not exist.",
},
},
"subset of providers in test file": {
configProviders: make(map[string]string),
fileProviders: map[string]string{
"bar": "source = \"testfile\"",
},
runProviders: []PassedProviderConfig{
{
InChild: &ProviderConfigRef{
Name: "foo",
},
InParent: &ProviderConfigRef{
Name: "bar",
},
},
},
expectedProviders: map[string]string{
"foo": "source = \"testfile\"",
},
},
"overrides providers in config": {
configProviders: map[string]string{
"foo": "source = \"config\"",
"bar": "source = \"config\"",
},
fileProviders: map[string]string{
"bar": "source = \"testfile\"",
},
expectedProviders: map[string]string{
"foo": "source = \"config\"",
"bar": "source = \"testfile\"",
},
},
"overrides subset of providers in config": {
configProviders: map[string]string{
"foo": "source = \"config\"",
"bar": "source = \"config\"",
},
fileProviders: map[string]string{
"foo": "source = \"testfile\"",
"bar": "source = \"testfile\"",
},
runProviders: []PassedProviderConfig{
{
InChild: &ProviderConfigRef{
Name: "bar",
},
InParent: &ProviderConfigRef{
Name: "bar",
},
},
},
expectedProviders: map[string]string{
"foo": "source = \"config\"",
"bar": "source = \"testfile\"",
},
},
"handles aliases": {
configProviders: map[string]string{
"foo.primary": "source = \"config\"",
"foo.secondary": "source = \"config\"",
},
fileProviders: map[string]string{
"foo": "source = \"testfile\"",
},
runProviders: []PassedProviderConfig{
{
InChild: &ProviderConfigRef{
Name: "foo.secondary",
},
InParent: &ProviderConfigRef{
Name: "foo",
},
},
},
expectedProviders: map[string]string{
"foo.primary": "source = \"config\"",
"foo.secondary": "source = \"testfile\"",
},
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
config := &Config{
Module: &Module{
ProviderConfigs: convertToProviders(t, tc.configProviders),
},
}
file := &TestFile{
Providers: convertToProviders(t, tc.fileProviders),
}
run := &TestRun{
Providers: tc.runProviders,
}
reset, diags := config.TransformForTest(run, file)
var actualErrs []string
for _, err := range diags.Errs() {
actualErrs = append(actualErrs, err.Error())
}
if diff := cmp.Diff(actualErrs, tc.expectedErrors, cmpopts.IgnoreUnexported()); len(diff) > 0 {
t.Errorf("unmatched errors\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", strings.Join(tc.expectedErrors, "\n"), strings.Join(actualErrs, "\n"), diff)
}
validate(t, "after transform mismatch", tc.expectedProviders, config.Module.ProviderConfigs)
reset()
validate(t, "after reset mismatch", tc.configProviders, config.Module.ProviderConfigs)
})
}
}