mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-23 07:33:32 -06:00
e802b23200
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
833 lines
29 KiB
Go
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)
|
|
|
|
})
|
|
}
|
|
}
|