mirror of https://github.com/hashicorp/terraform
testing framework: implement overrides in terraform graph (#34169)
* testing framework: implement overrides in terraform graph * fix bug for modules and module instances * add test for ModuleInstance.ContainingModule()pull/32328/merge
parent
1974c9ec16
commit
4ce385a19b
@ -0,0 +1,204 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package mocking
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
)
|
||||
|
||||
// Overrides contains a summary of all the overrides that should apply for a
|
||||
// test run.
|
||||
//
|
||||
// This requires us to deduplicate between run blocks and test files, and mock
|
||||
// providers.
|
||||
type Overrides struct {
|
||||
providerOverrides map[string]addrs.Map[addrs.Targetable, *configs.Override]
|
||||
localOverrides addrs.Map[addrs.Targetable, *configs.Override]
|
||||
}
|
||||
|
||||
func PackageOverrides(run *configs.TestRun, file *configs.TestFile, config *configs.Config) *Overrides {
|
||||
overrides := &Overrides{
|
||||
providerOverrides: make(map[string]addrs.Map[addrs.Targetable, *configs.Override]),
|
||||
localOverrides: addrs.MakeMap[addrs.Targetable, *configs.Override](),
|
||||
}
|
||||
|
||||
// The run block overrides have the highest priority, we always include all
|
||||
// of them.
|
||||
for _, elem := range run.Overrides.Elems {
|
||||
overrides.localOverrides.PutElement(elem)
|
||||
}
|
||||
|
||||
// The file overrides are second, we include these as long as there isn't
|
||||
// a direct replacement in the current run block or the run block doesn't
|
||||
// override an entire module that a file override would be inside.
|
||||
for _, elem := range file.Overrides.Elems {
|
||||
target := elem.Key
|
||||
|
||||
if overrides.localOverrides.Has(target) {
|
||||
// The run block provided a value already.
|
||||
continue
|
||||
}
|
||||
|
||||
overrides.localOverrides.PutElement(elem)
|
||||
}
|
||||
|
||||
// Finally, we want to include the overrides for any mock providers we have.
|
||||
for key, provider := range config.Module.ProviderConfigs {
|
||||
if !provider.Mock {
|
||||
// Only mock providers can supply overrides.
|
||||
continue
|
||||
}
|
||||
|
||||
for _, elem := range provider.MockData.Overrides.Elems {
|
||||
target := elem.Key
|
||||
|
||||
if overrides.localOverrides.Has(target) {
|
||||
// Then the file or the run block is providing an override with
|
||||
// higher precedence.
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := overrides.providerOverrides[key]; !exists {
|
||||
overrides.providerOverrides[key] = addrs.MakeMap[addrs.Targetable, *configs.Override]()
|
||||
}
|
||||
overrides.providerOverrides[key].PutElement(elem)
|
||||
}
|
||||
}
|
||||
|
||||
return overrides
|
||||
}
|
||||
|
||||
// IsOverridden returns true if the module is either overridden directly or
|
||||
// nested within another module that is already being overridden.
|
||||
//
|
||||
// For this function, we know that overrides defined within mock providers
|
||||
// cannot target modules directly. Therefore, we only need to check the local
|
||||
// overrides within this function.
|
||||
func (overrides *Overrides) IsOverridden(module addrs.ModuleInstance) bool {
|
||||
if overrides.localOverrides.Has(module) {
|
||||
// Short circuit things, if we have an exact match just return now.
|
||||
return true
|
||||
}
|
||||
|
||||
// Otherwise, check for parents.
|
||||
for _, elem := range overrides.localOverrides.Elems {
|
||||
if elem.Key.TargetContains(module) {
|
||||
// Then we have an ancestor of module being overridden instead of
|
||||
// module being overridden directly.
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsDeeplyOverridden returns true if an ancestor of this module is overridden
|
||||
// but not if the module is overridden directly.
|
||||
//
|
||||
// This function doesn't consider an instanced module to be deeply overridden
|
||||
// by the uninstanced reference to the same module. So,
|
||||
// IsDeeplyOverridden("mod.child[0]") would return false if "mod.child" has been
|
||||
// overridden.
|
||||
//
|
||||
// For this function, we know that overrides defined within mock providers
|
||||
// cannot target modules directly. Therefore, we only need to check the local
|
||||
// overrides within this function.
|
||||
func (overrides *Overrides) IsDeeplyOverridden(module addrs.ModuleInstance) bool {
|
||||
for _, elem := range overrides.localOverrides.Elems {
|
||||
target := elem.Key
|
||||
|
||||
if target.TargetContains(module) {
|
||||
// So we do think it contains it, but it could be matching here
|
||||
// because of equality or because we have an instanced module.
|
||||
if instance, ok := target.(addrs.ModuleInstance); ok {
|
||||
if instance.Equal(module) {
|
||||
// Then we're exactly equal, so not deeply nested.
|
||||
continue
|
||||
}
|
||||
|
||||
if instance.Module().Equal(module.Module()) {
|
||||
// Then we're an instanced version of they other one, so
|
||||
// also not deeply nested by our definition of deeply.
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Otherwise, it's deeply nested.
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetOverrideInclProviders retrieves the override for target if it exists.
|
||||
//
|
||||
// This function also checks the provider specific overrides using the provider
|
||||
// argument.
|
||||
func (overrides *Overrides) GetOverrideInclProviders(target addrs.Targetable, provider addrs.AbsProviderConfig) (*configs.Override, bool) {
|
||||
// If we have a local override, then apply that first.
|
||||
if override, ok := overrides.GetOverride(target); ok {
|
||||
return override, true
|
||||
}
|
||||
|
||||
// Otherwise, check if we have overrides for this provider.
|
||||
providerOverrides, ok := overrides.ProviderMatch(provider)
|
||||
if ok {
|
||||
if override, ok := providerOverrides.GetOk(target); ok {
|
||||
return override, true
|
||||
}
|
||||
}
|
||||
|
||||
// If we have no overrides, that's okay.
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// GetOverride retrieves the override for target from the local overrides if
|
||||
// it exists.
|
||||
func (overrides *Overrides) GetOverride(target addrs.Targetable) (*configs.Override, bool) {
|
||||
return overrides.localOverrides.GetOk(target)
|
||||
}
|
||||
|
||||
// ProviderMatch returns true if we have overrides for the given provider.
|
||||
//
|
||||
// This is so that we can selectively apply overrides to resources that are
|
||||
// being supplied by a given provider.
|
||||
func (overrides *Overrides) ProviderMatch(provider addrs.AbsProviderConfig) (addrs.Map[addrs.Targetable, *configs.Override], bool) {
|
||||
if !provider.Module.IsRoot() {
|
||||
// We can only set mock providers within the root module.
|
||||
return addrs.Map[addrs.Targetable, *configs.Override]{}, false
|
||||
}
|
||||
|
||||
name := provider.Provider.Type
|
||||
if len(provider.Alias) > 0 {
|
||||
name = fmt.Sprintf("%s.%s", name, provider.Alias)
|
||||
}
|
||||
|
||||
data, exists := overrides.providerOverrides[name]
|
||||
return data, exists
|
||||
}
|
||||
|
||||
// Empty returns true if we have no actual overrides.
|
||||
//
|
||||
// This is just a convenience function to make checking for overrides easier.
|
||||
func (overrides *Overrides) Empty() bool {
|
||||
if overrides == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if overrides.localOverrides.Len() > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, value := range overrides.providerOverrides {
|
||||
if value.Len() > 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -0,0 +1,119 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package mocking
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
)
|
||||
|
||||
func TestPackageOverrides(t *testing.T) {
|
||||
mustResourceInstance := func(s string) addrs.AbsResourceInstance {
|
||||
addr, diags := addrs.ParseAbsResourceInstanceStr(s)
|
||||
if len(diags) > 0 {
|
||||
t.Fatal(diags)
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
primary := mustResourceInstance("test_instance.primary")
|
||||
secondary := mustResourceInstance("test_instance.secondary")
|
||||
tertiary := mustResourceInstance("test_instance.tertiary")
|
||||
|
||||
testrun := mustResourceInstance("test_instance.test_run")
|
||||
testfile := mustResourceInstance("test_instance.test_file")
|
||||
provider := mustResourceInstance("test_instance.provider")
|
||||
|
||||
// Add a single override to the test run.
|
||||
run := &configs.TestRun{
|
||||
Overrides: addrs.MakeMap[addrs.Targetable, *configs.Override](),
|
||||
}
|
||||
run.Overrides.Put(primary, &configs.Override{
|
||||
Target: &addrs.Target{
|
||||
Subject: testrun,
|
||||
},
|
||||
})
|
||||
|
||||
// Add a unique item to the test file, and duplicate the test run data.
|
||||
file := &configs.TestFile{
|
||||
Overrides: addrs.MakeMap[addrs.Targetable, *configs.Override](),
|
||||
}
|
||||
file.Overrides.Put(primary, &configs.Override{
|
||||
Target: &addrs.Target{
|
||||
Subject: testfile,
|
||||
},
|
||||
})
|
||||
file.Overrides.Put(secondary, &configs.Override{
|
||||
Target: &addrs.Target{
|
||||
Subject: testfile,
|
||||
},
|
||||
})
|
||||
|
||||
// Add all data from the file and run block are duplicating here, and then
|
||||
// a unique one.
|
||||
config := &configs.Config{
|
||||
Module: &configs.Module{
|
||||
ProviderConfigs: map[string]*configs.Provider{
|
||||
"mock": {
|
||||
Mock: true,
|
||||
MockData: &configs.MockData{
|
||||
Overrides: addrs.MakeMap[addrs.Targetable, *configs.Override](),
|
||||
},
|
||||
},
|
||||
"real": {},
|
||||
},
|
||||
},
|
||||
}
|
||||
config.Module.ProviderConfigs["mock"].MockData.Overrides.Put(primary, &configs.Override{
|
||||
Target: &addrs.Target{
|
||||
Subject: provider,
|
||||
},
|
||||
})
|
||||
config.Module.ProviderConfigs["mock"].MockData.Overrides.Put(secondary, &configs.Override{
|
||||
Target: &addrs.Target{
|
||||
Subject: provider,
|
||||
},
|
||||
})
|
||||
config.Module.ProviderConfigs["mock"].MockData.Overrides.Put(tertiary, &configs.Override{
|
||||
Target: &addrs.Target{
|
||||
Subject: provider,
|
||||
},
|
||||
})
|
||||
|
||||
overrides := PackageOverrides(run, file, config)
|
||||
|
||||
// We now expect that the run and file overrides took precedence.
|
||||
first, pOk := overrides.GetOverride(primary)
|
||||
second, sOk := overrides.GetOverride(secondary)
|
||||
third, tOk := overrides.GetOverrideInclProviders(tertiary, addrs.AbsProviderConfig{
|
||||
Provider: addrs.Provider{
|
||||
Type: "mock",
|
||||
},
|
||||
})
|
||||
|
||||
if !pOk || !sOk || !tOk {
|
||||
t.Fatalf("expected to find all overrides, but got %t %t %t", pOk, sOk, tOk)
|
||||
}
|
||||
|
||||
if !first.Target.Subject.(addrs.AbsResourceInstance).Equal(testrun) {
|
||||
t.Errorf("expected %s but got %s for primary", testrun, first.Target.Subject)
|
||||
}
|
||||
|
||||
if !second.Target.Subject.(addrs.AbsResourceInstance).Equal(testfile) {
|
||||
t.Errorf("expected %s but got %s for primary", testfile, second.Target.Subject)
|
||||
}
|
||||
|
||||
if !third.Target.Subject.(addrs.AbsResourceInstance).Equal(provider) {
|
||||
t.Errorf("expected %s but got %s for primary", provider, third.Target.Subject)
|
||||
}
|
||||
|
||||
// Also, final sanity check.
|
||||
_, ok := overrides.providerOverrides["real"]
|
||||
if ok {
|
||||
t.Errorf("shouldn't have stored the real provider but did")
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package mocking
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
)
|
||||
|
||||
type InitProviderOverrides func(map[string]addrs.Map[addrs.Targetable, *configs.Override])
|
||||
type InitLocalOverrides func(addrs.Map[addrs.Targetable, *configs.Override])
|
||||
|
||||
func OverridesForTesting(providers InitProviderOverrides, locals InitLocalOverrides) *Overrides {
|
||||
overrides := &Overrides{
|
||||
providerOverrides: make(map[string]addrs.Map[addrs.Targetable, *configs.Override]),
|
||||
localOverrides: addrs.MakeMap[addrs.Targetable, *configs.Override](),
|
||||
}
|
||||
|
||||
if providers != nil {
|
||||
providers(overrides.providerOverrides)
|
||||
}
|
||||
|
||||
if locals != nil {
|
||||
locals(overrides.localOverrides)
|
||||
}
|
||||
|
||||
return overrides
|
||||
}
|
||||
@ -0,0 +1,614 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
"github.com/hashicorp/terraform/internal/moduletest/mocking"
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
"github.com/hashicorp/terraform/internal/states"
|
||||
)
|
||||
|
||||
// This file contains 'integration' tests for the Terraform test overrides
|
||||
// functionality.
|
||||
//
|
||||
// These tests could live in context_apply_test or context_apply2_test but given
|
||||
// the size of those files, it makes sense to keep these tests grouped together.
|
||||
|
||||
func TestContextOverrides(t *testing.T) {
|
||||
|
||||
// The approach to the testing here, is to create some configuration that
|
||||
// would panic if executed normally because of the underlying provider.
|
||||
//
|
||||
// We then write overrides that make sure the underlying provider is never
|
||||
// called.
|
||||
//
|
||||
// We then run a plan, apply, refresh, destroy sequence that tests all the
|
||||
// potential function calls to the underlying provider to make sure we
|
||||
// have covered everything.
|
||||
//
|
||||
// Finally, we validate some expected values after the apply stage to make
|
||||
// sure the overrides are returning the values we want them to.
|
||||
|
||||
tcs := map[string]struct {
|
||||
configs map[string]string
|
||||
overrides *mocking.Overrides
|
||||
outputs cty.Value
|
||||
}{
|
||||
"resource": {
|
||||
configs: map[string]string{
|
||||
"main.tf": `
|
||||
resource "test_instance" "instance" {
|
||||
value = "Hello, world!"
|
||||
}
|
||||
|
||||
output "value" {
|
||||
value = test_instance.instance.value
|
||||
}
|
||||
|
||||
output "id" {
|
||||
value = test_instance.instance.id
|
||||
}`,
|
||||
},
|
||||
overrides: mocking.OverridesForTesting(nil, func(overrides addrs.Map[addrs.Targetable, *configs.Override]) {
|
||||
overrides.Put(mustResourceInstanceAddr("test_instance.instance"), &configs.Override{
|
||||
Values: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("h3ll0"),
|
||||
}),
|
||||
})
|
||||
}),
|
||||
outputs: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("h3ll0"),
|
||||
"value": cty.StringVal("Hello, world!"),
|
||||
}),
|
||||
},
|
||||
"resource_from_provider": {
|
||||
configs: map[string]string{
|
||||
"main.tf": `
|
||||
provider "test" {}
|
||||
|
||||
resource "test_instance" "instance" {
|
||||
value = "Hello, world!"
|
||||
}
|
||||
|
||||
output "value" {
|
||||
value = test_instance.instance.value
|
||||
}
|
||||
|
||||
output "id" {
|
||||
value = test_instance.instance.id
|
||||
}`,
|
||||
},
|
||||
overrides: mocking.OverridesForTesting(func(overrides map[string]addrs.Map[addrs.Targetable, *configs.Override]) {
|
||||
overrides["test"] = addrs.MakeMap[addrs.Targetable, *configs.Override]()
|
||||
overrides["test"].Put(mustResourceInstanceAddr("test_instance.instance"), &configs.Override{
|
||||
Values: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("h3ll0"),
|
||||
}),
|
||||
})
|
||||
}, nil),
|
||||
outputs: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("h3ll0"),
|
||||
"value": cty.StringVal("Hello, world!"),
|
||||
}),
|
||||
},
|
||||
"selectively_applies_provider": {
|
||||
configs: map[string]string{
|
||||
"main.tf": `
|
||||
provider "test" {}
|
||||
|
||||
provider "test" {
|
||||
alias = "secondary"
|
||||
}
|
||||
|
||||
resource "test_instance" "primary" {
|
||||
value = "primary"
|
||||
}
|
||||
|
||||
resource "test_instance" "secondary" {
|
||||
provider = test.secondary
|
||||
value = "secondary"
|
||||
}
|
||||
|
||||
output "primary_value" {
|
||||
value = test_instance.primary.value
|
||||
}
|
||||
|
||||
output "primary_id" {
|
||||
value = test_instance.primary.id
|
||||
}
|
||||
|
||||
output "secondary_value" {
|
||||
value = test_instance.secondary.value
|
||||
}
|
||||
|
||||
output "secondary_id" {
|
||||
value = test_instance.secondary.id
|
||||
}`,
|
||||
},
|
||||
overrides: mocking.OverridesForTesting(func(overrides map[string]addrs.Map[addrs.Targetable, *configs.Override]) {
|
||||
overrides["test.secondary"] = addrs.MakeMap[addrs.Targetable, *configs.Override]()
|
||||
// Test should not apply this override, as this provider is
|
||||
// not being used for this resource.
|
||||
overrides["test.secondary"].Put(mustResourceInstanceAddr("test_instance.primary"), &configs.Override{
|
||||
Values: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("primary_id"),
|
||||
}),
|
||||
})
|
||||
overrides["test.secondary"].Put(mustResourceInstanceAddr("test_instance.secondary"), &configs.Override{
|
||||
Values: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("secondary_id"),
|
||||
}),
|
||||
})
|
||||
}, func(overrides addrs.Map[addrs.Targetable, *configs.Override]) {
|
||||
overrides.Put(mustResourceInstanceAddr("test_instance.primary"), &configs.Override{
|
||||
Values: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("h3ll0"),
|
||||
}),
|
||||
})
|
||||
}),
|
||||
outputs: cty.ObjectVal(map[string]cty.Value{
|
||||
"primary_id": cty.StringVal("h3ll0"),
|
||||
"primary_value": cty.StringVal("primary"),
|
||||
"secondary_id": cty.StringVal("secondary_id"),
|
||||
"secondary_value": cty.StringVal("secondary"),
|
||||
}),
|
||||
},
|
||||
"propagates_provider_to_modules_explicit": {
|
||||
configs: map[string]string{
|
||||
"main.tf": `
|
||||
provider "test" {}
|
||||
|
||||
module "mod" {
|
||||
source = "./mod"
|
||||
|
||||
providers = {
|
||||
test = test
|
||||
}
|
||||
}
|
||||
|
||||
output "value" {
|
||||
value = module.mod.value
|
||||
}
|
||||
|
||||
output "id" {
|
||||
value = module.mod.id
|
||||
}`,
|
||||
"mod/main.tf": `
|
||||
provider "test" {}
|
||||
|
||||
resource "test_instance" "instance" {
|
||||
value = "Hello, world!"
|
||||
}
|
||||
|
||||
output "value" {
|
||||
value = test_instance.instance.value
|
||||
}
|
||||
|
||||
output "id" {
|
||||
value = test_instance.instance.id
|
||||
}
|
||||
`,
|
||||
},
|
||||
overrides: mocking.OverridesForTesting(func(overrides map[string]addrs.Map[addrs.Targetable, *configs.Override]) {
|
||||
overrides["test"] = addrs.MakeMap[addrs.Targetable, *configs.Override]()
|
||||
overrides["test"].Put(mustResourceInstanceAddr("module.mod.test_instance.instance"), &configs.Override{
|
||||
Values: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("h3ll0"),
|
||||
}),
|
||||
})
|
||||
}, nil),
|
||||
outputs: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("h3ll0"),
|
||||
"value": cty.StringVal("Hello, world!"),
|
||||
}),
|
||||
},
|
||||
"propagates_provider_to_modules_implicit": {
|
||||
configs: map[string]string{
|
||||
"main.tf": `
|
||||
provider "test" {}
|
||||
|
||||
module "mod" {
|
||||
source = "./mod"
|
||||
}
|
||||
|
||||
output "value" {
|
||||
value = module.mod.value
|
||||
}
|
||||
|
||||
output "id" {
|
||||
value = module.mod.id
|
||||
}`,
|
||||
"mod/main.tf": `
|
||||
resource "test_instance" "instance" {
|
||||
value = "Hello, world!"
|
||||
}
|
||||
|
||||
output "value" {
|
||||
value = test_instance.instance.value
|
||||
}
|
||||
|
||||
output "id" {
|
||||
value = test_instance.instance.id
|
||||
}
|
||||
|
||||
`,
|
||||
},
|
||||
overrides: mocking.OverridesForTesting(func(overrides map[string]addrs.Map[addrs.Targetable, *configs.Override]) {
|
||||
overrides["test"] = addrs.MakeMap[addrs.Targetable, *configs.Override]()
|
||||
overrides["test"].Put(mustResourceInstanceAddr("module.mod.test_instance.instance"), &configs.Override{
|
||||
Values: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("h3ll0"),
|
||||
}),
|
||||
})
|
||||
}, nil),
|
||||
outputs: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("h3ll0"),
|
||||
"value": cty.StringVal("Hello, world!"),
|
||||
}),
|
||||
},
|
||||
"data_source": {
|
||||
configs: map[string]string{
|
||||
"main.tf": `
|
||||
data "test_instance" "instance" {
|
||||
id = "data-source"
|
||||
}
|
||||
|
||||
resource "test_instance" "instance" {
|
||||
value = data.test_instance.instance.value
|
||||
}
|
||||
|
||||
output "value" {
|
||||
value = test_instance.instance.value
|
||||
}
|
||||
|
||||
output "id" {
|
||||
value = test_instance.instance.id
|
||||
}`,
|
||||
},
|
||||
overrides: mocking.OverridesForTesting(nil, func(overrides addrs.Map[addrs.Targetable, *configs.Override]) {
|
||||
overrides.Put(mustResourceInstanceAddr("test_instance.instance"), &configs.Override{
|
||||
Values: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("h3ll0"),
|
||||
}),
|
||||
})
|
||||
overrides.Put(mustResourceInstanceAddr("data.test_instance.instance"), &configs.Override{
|
||||
Values: cty.ObjectVal(map[string]cty.Value{
|
||||
"value": cty.StringVal("Hello, world!"),
|
||||
}),
|
||||
})
|
||||
}),
|
||||
outputs: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("h3ll0"),
|
||||
"value": cty.StringVal("Hello, world!"),
|
||||
}),
|
||||
},
|
||||
"module": {
|
||||
configs: map[string]string{
|
||||
"main.tf": `
|
||||
module "mod" {
|
||||
source = "./mod"
|
||||
}
|
||||
|
||||
output "value" {
|
||||
value = module.mod.value
|
||||
}
|
||||
|
||||
output "id" {
|
||||
value = module.mod.id
|
||||
}`,
|
||||
"mod/main.tf": `
|
||||
resource "test_instance" "instance" {
|
||||
value = "random"
|
||||
}
|
||||
|
||||
output "value" {
|
||||
value = test_instance.instance.value
|
||||
}
|
||||
|
||||
output "id" {
|
||||
value = test_instance.instance.id
|
||||
}
|
||||
|
||||
check "value" {
|
||||
assert {
|
||||
condition = test_instance.instance.value == "definitely wrong"
|
||||
error_message = "bad value"
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
overrides: mocking.OverridesForTesting(nil, func(overrides addrs.Map[addrs.Targetable, *configs.Override]) {
|
||||
overrides.Put(mustModuleInstance("module.mod"), &configs.Override{
|
||||
Values: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("h3ll0"),
|
||||
"value": cty.StringVal("Hello, world!"),
|
||||
}),
|
||||
})
|
||||
}),
|
||||
outputs: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("h3ll0"),
|
||||
"value": cty.StringVal("Hello, world!"),
|
||||
}),
|
||||
},
|
||||
"provider_type_override": {
|
||||
configs: map[string]string{
|
||||
"main.tf": `
|
||||
provider "test" {}
|
||||
|
||||
module "mod" {
|
||||
source = "./mod"
|
||||
}
|
||||
|
||||
output "value" {
|
||||
value = module.mod.value
|
||||
}
|
||||
|
||||
output "id" {
|
||||
value = module.mod.id
|
||||
}`,
|
||||
"mod/main.tf": `
|
||||
terraform {
|
||||
required_providers {
|
||||
replaced = {
|
||||
source = "hashicorp/test"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "test_instance" "instance" {
|
||||
provider = replaced
|
||||
value = "Hello, world!"
|
||||
}
|
||||
|
||||
output "value" {
|
||||
value = test_instance.instance.value
|
||||
}
|
||||
|
||||
output "id" {
|
||||
value = test_instance.instance.id
|
||||
}
|
||||
|
||||
`,
|
||||
},
|
||||
overrides: mocking.OverridesForTesting(func(overrides map[string]addrs.Map[addrs.Targetable, *configs.Override]) {
|
||||
overrides["test"] = addrs.MakeMap[addrs.Targetable, *configs.Override]()
|
||||
overrides["test"].Put(mustResourceInstanceAddr("module.mod.test_instance.instance"), &configs.Override{
|
||||
Values: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("h3ll0"),
|
||||
}),
|
||||
})
|
||||
}, nil),
|
||||
outputs: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("h3ll0"),
|
||||
"value": cty.StringVal("Hello, world!"),
|
||||
}),
|
||||
},
|
||||
"resource_instance_overrides": {
|
||||
configs: map[string]string{
|
||||
"main.tf": `
|
||||
provider "test" {}
|
||||
|
||||
resource "test_instance" "instance" {
|
||||
count = 3
|
||||
value = "Hello, world!"
|
||||
}
|
||||
|
||||
output "value" {
|
||||
value = test_instance.instance.*.value
|
||||
}
|
||||
|
||||
output "id" {
|
||||
value = test_instance.instance.*.id
|
||||
}`,
|
||||
},
|
||||
overrides: mocking.OverridesForTesting(nil, func(overrides addrs.Map[addrs.Targetable, *configs.Override]) {
|
||||
overrides.Put(mustAbsResourceAddr("test_instance.instance"), &configs.Override{
|
||||
Values: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("generic"),
|
||||
}),
|
||||
})
|
||||
overrides.Put(mustResourceInstanceAddr("test_instance.instance[1]"), &configs.Override{
|
||||
Values: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("specific"),
|
||||
}),
|
||||
})
|
||||
}),
|
||||
outputs: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.TupleVal([]cty.Value{
|
||||
cty.StringVal("generic"),
|
||||
cty.StringVal("specific"),
|
||||
cty.StringVal("generic"),
|
||||
}),
|
||||
"value": cty.TupleVal([]cty.Value{
|
||||
cty.StringVal("Hello, world!"),
|
||||
cty.StringVal("Hello, world!"),
|
||||
cty.StringVal("Hello, world!"),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
"module_instance_overrides": {
|
||||
configs: map[string]string{
|
||||
"main.tf": `
|
||||
provider "test" {}
|
||||
|
||||
module "mod" {
|
||||
count = 3
|
||||
source = "./mod"
|
||||
}
|
||||
|
||||
output "value" {
|
||||
value = module.mod.*.value
|
||||
}
|
||||
|
||||
output "id" {
|
||||
value = module.mod.*.id
|
||||
}`,
|
||||
"mod/main.tf": `
|
||||
terraform {
|
||||
required_providers {
|
||||
replaced = {
|
||||
source = "hashicorp/test"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "test_instance" "instance" {
|
||||
provider = replaced
|
||||
value = "Hello, world!"
|
||||
}
|
||||
|
||||
output "value" {
|
||||
value = test_instance.instance.value
|
||||
}
|
||||
|
||||
output "id" {
|
||||
value = test_instance.instance.id
|
||||
}
|
||||
|
||||
`,
|
||||
},
|
||||
overrides: mocking.OverridesForTesting(nil, func(overrides addrs.Map[addrs.Targetable, *configs.Override]) {
|
||||
overrides.Put(mustModuleInstance("module.mod"), &configs.Override{
|
||||
Values: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("generic"),
|
||||
"value": cty.StringVal("Hello, world!"),
|
||||
}),
|
||||
})
|
||||
overrides.Put(mustModuleInstance("module.mod[1]"), &configs.Override{
|
||||
Values: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("specific"),
|
||||
"value": cty.StringVal("Hello, world!"),
|
||||
}),
|
||||
})
|
||||
}),
|
||||
outputs: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.TupleVal([]cty.Value{
|
||||
cty.StringVal("generic"),
|
||||
cty.StringVal("specific"),
|
||||
cty.StringVal("generic"),
|
||||
}),
|
||||
"value": cty.TupleVal([]cty.Value{
|
||||
cty.StringVal("Hello, world!"),
|
||||
cty.StringVal("Hello, world!"),
|
||||
cty.StringVal("Hello, world!"),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}
|
||||
for name, tc := range tcs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
cfg := testModuleInline(t, tc.configs)
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("test"): testProviderFuncFixed(underlyingOverridesProvider),
|
||||
},
|
||||
})
|
||||
|
||||
plan, diags := ctx.Plan(cfg, states.NewState(), &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
Overrides: tc.overrides,
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.Err())
|
||||
}
|
||||
|
||||
state, diags := ctx.Apply(plan, cfg)
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.Err())
|
||||
}
|
||||
|
||||
outputs := make(map[string]cty.Value, len(cfg.Module.Outputs))
|
||||
for _, output := range cfg.Module.Outputs {
|
||||
outputs[output.Name] = state.OutputValue(output.Addr().Absolute(addrs.RootModuleInstance)).Value
|
||||
}
|
||||
actual := cty.ObjectVal(outputs)
|
||||
|
||||
if !actual.RawEquals(tc.outputs) {
|
||||
t.Fatalf("expected:\n%s\nactual:\n%s", tc.outputs.GoString(), actual.GoString())
|
||||
}
|
||||
|
||||
_, diags = ctx.Plan(cfg, state, &PlanOpts{
|
||||
Mode: plans.RefreshOnlyMode,
|
||||
Overrides: tc.overrides,
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.Err())
|
||||
}
|
||||
|
||||
destroyPlan, diags := ctx.Plan(cfg, state, &PlanOpts{
|
||||
Mode: plans.DestroyMode,
|
||||
Overrides: tc.overrides,
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.Err())
|
||||
}
|
||||
|
||||
_, diags = ctx.Apply(destroyPlan, cfg)
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.Err())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// underlyingOverridesProvider returns a provider that always panics for
|
||||
// important calls. This is to validate the behaviour of the overrides
|
||||
// functionality, in that they should stop the provider from being executed.
|
||||
var underlyingOverridesProvider = &MockProvider{
|
||||
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
|
||||
ResourceTypes: map[string]providers.Schema{
|
||||
"test_instance": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
"value": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
DataSources: map[string]providers.Schema{
|
||||
"test_instance": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
"value": {
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ReadResourceFn: func(request providers.ReadResourceRequest) providers.ReadResourceResponse {
|
||||
panic("ReadResourceFn called, should have been overridden.")
|
||||
},
|
||||
PlanResourceChangeFn: func(request providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
||||
panic("PlanResourceChangeFn called, should have been overridden.")
|
||||
},
|
||||
ApplyResourceChangeFn: func(request providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
||||
panic("ApplyResourceChangeFn called, should have been overridden.")
|
||||
},
|
||||
ReadDataSourceFn: func(request providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
||||
panic("ReadDataSourceFn called, should have been overridden.")
|
||||
},
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
)
|
||||
|
||||
// GraphNodeOverridable represents a node in the graph that can be overridden
|
||||
// by the testing framework.
|
||||
type GraphNodeOverridable interface {
|
||||
GraphNodeResourceInstance
|
||||
|
||||
ConfigProvider() addrs.AbsProviderConfig
|
||||
SetOverride(override *configs.Override)
|
||||
}
|
||||
Loading…
Reference in new issue