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
Liam Cervante 2 years ago committed by GitHub
parent 1974c9ec16
commit 4ce385a19b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -504,6 +504,18 @@ func (m ModuleInstance) Module() Module {
return ret
}
// ContainingModule returns the address of the module instance as if the last
// step wasn't instanced. For example, it turns module.child[0] into
// module.child and module[0].child[0] into module[0].child.
func (m ModuleInstance) ContainingModule() ModuleInstance {
if len(m) == 0 {
return nil
}
ret := m.Parent()
return ret.Child(m[len(m)-1].Name, NoKey)
}
func (m ModuleInstance) AddrType() TargetableAddrType {
return ModuleInstanceAddrType
}

@ -164,6 +164,47 @@ func TestModuleInstance_IsDeclaredByCall(t *testing.T) {
}
}
func TestModuleInstance_ContainingModule(t *testing.T) {
tcs := map[string]struct {
module string
expected string
}{
"no_instances": {
module: "module.parent.module.child",
expected: "module.parent.module.child",
},
"last_instance": {
module: "module.parent.module.child[0]",
expected: "module.parent.module.child",
},
"middle_instance": {
module: "module.parent[0].module.child",
expected: "module.parent[0].module.child",
},
"all_instances": {
module: "module.parent[0].module.child[0]",
expected: "module.parent[0].module.child",
},
"single_no_instance": {
module: "module.parent",
expected: "module.parent",
},
"single_instance": {
module: "module.parent[0]",
expected: "module.parent",
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
module := mustParseModuleInstanceStr(tc.module)
actual, expected := module.ContainingModule().String(), tc.expected
if actual != expected {
t.Errorf("expected: %s\nactual: %s", expected, actual)
}
})
}
}
func mustParseModuleInstanceStr(str string) ModuleInstance {
mi, diags := ParseModuleInstanceStr(str)
if diags.HasErrors() {

@ -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
}

@ -12,6 +12,7 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/lang/globalref"
"github.com/hashicorp/terraform/internal/moduletest/mocking"
"github.com/hashicorp/terraform/internal/states"
)
@ -112,6 +113,13 @@ type Plan struct {
// representation of the plan.
ExternalReferences []*addrs.Reference
// Overrides contains the set of overrides that were applied while making
// this plan. We need to provide the same set of overrides when applying
// the plan so we preserve them here. As with PlannedState and
// ExternalReferences, this is only used by the testing framework and so
// isn't written into any external representation of the plan.
Overrides *mocking.Overrides
// Timestamp is the record of truth for when the plan happened.
Timestamp time.Time
}

@ -62,6 +62,7 @@ func (c *Context) Apply(plan *plans.Plan, config *configs.Config) (*states.State
Config: config,
InputState: workingState,
Changes: plan.Changes,
Overrides: plan.Overrides,
// We need to propagate the check results from the plan phase,
// because that will tell us which checkable objects we're expecting

@ -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.")
},
}

@ -17,6 +17,7 @@ import (
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/lang/globalref"
"github.com/hashicorp/terraform/internal/moduletest/mocking"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/refactoring"
"github.com/hashicorp/terraform/internal/states"
@ -79,6 +80,10 @@ type PlanOpts struct {
// the actual graph.
ExternalReferences []*addrs.Reference
// Overrides provides a set of override objects that should be applied
// during this plan.
Overrides *mocking.Overrides
// ImportTargets is a list of target resources to import. These resources
// will be added to the plan graph.
ImportTargets []*ImportTarget
@ -570,6 +575,7 @@ func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, o
Changes: changes,
MoveResults: moveResults,
PlanTimeTimestamp: timestamp,
Overrides: opts.Overrides,
})
diags = diags.Append(walker.NonFatalDiagnostics)
diags = diags.Append(walkDiags)
@ -620,6 +626,7 @@ func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, o
PriorState: priorState,
PlannedState: walker.State.Close(),
ExternalReferences: opts.ExternalReferences,
Overrides: opts.Overrides,
Checks: states.NewCheckResults(walker.Checks),
Timestamp: timestamp,

@ -10,6 +10,7 @@ import (
"github.com/hashicorp/terraform/internal/checks"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/moduletest/mocking"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/refactoring"
"github.com/hashicorp/terraform/internal/states"
@ -41,6 +42,10 @@ type graphWalkOpts struct {
// the apply phase.
PlanTimeTimestamp time.Time
// Overrides contains the set of overrides we should apply during this
// operation.
Overrides *mocking.Overrides
MoveResults refactoring.MoveResults
}
@ -150,5 +155,6 @@ func (c *Context) graphWalker(operation walkOperation, opts *graphWalkOpts) *Con
Operation: operation,
StopContext: c.runContext,
PlanTimestamp: opts.PlanTimeTimestamp,
Overrides: opts.Overrides,
}
}

@ -13,6 +13,7 @@ import (
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/lang"
"github.com/hashicorp/terraform/internal/moduletest/mocking"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/provisioners"
@ -203,6 +204,10 @@ type EvalContext interface {
// objects accessible through it.
MoveResults() refactoring.MoveResults
// Overrides contains the modules and resources we should mock as part of
// this execution.
Overrides() *mocking.Overrides
// WithPath returns a copy of the context with the internal path set to the
// path argument.
WithPath(path addrs.ModuleInstance) EvalContext

@ -18,6 +18,7 @@ import (
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/lang"
"github.com/hashicorp/terraform/internal/moduletest/mocking"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/provisioners"
@ -74,6 +75,7 @@ type BuiltinEvalContext struct {
PrevRunStateValue *states.SyncState
InstanceExpanderValue *instances.Expander
MoveResultsValue refactoring.MoveResults
OverrideValues *mocking.Overrides
}
// BuiltinEvalContext implements EvalContext
@ -520,3 +522,7 @@ func (ctx *BuiltinEvalContext) InstanceExpander() *instances.Expander {
func (ctx *BuiltinEvalContext) MoveResults() refactoring.MoveResults {
return ctx.MoveResultsValue
}
func (ctx *BuiltinEvalContext) Overrides() *mocking.Overrides {
return ctx.OverrideValues
}

@ -15,6 +15,7 @@ import (
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/lang"
"github.com/hashicorp/terraform/internal/moduletest/mocking"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/provisioners"
@ -153,6 +154,9 @@ type MockEvalContext struct {
InstanceExpanderCalled bool
InstanceExpanderExpander *instances.Expander
OverridesCalled bool
OverrideValues *mocking.Overrides
}
// MockEvalContext implements EvalContext
@ -404,3 +408,8 @@ func (c *MockEvalContext) InstanceExpander() *instances.Expander {
c.InstanceExpanderCalled = true
return c.InstanceExpanderExpander
}
func (c *MockEvalContext) Overrides() *mocking.Overrides {
c.OverridesCalled = true
return c.OverrideValues
}

@ -8,7 +8,10 @@ import (
"log"
"strings"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/moduletest/mocking"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/hashicorp/terraform/internal/addrs"
@ -73,6 +76,12 @@ func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics {
defer walker.ExitPath(pn.Path())
}
if g.checkAndApplyOverrides(ctx.Overrides(), v) {
// We can skip whole vertices if they are in a module that has been
// overridden.
return
}
// If the node is exec-able, then execute it.
if ev, ok := v.(GraphNodeExecutable); ok {
diags = diags.Append(walker.Execute(vertexCtx, ev))
@ -136,3 +145,97 @@ func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics {
return g.AcyclicGraph.Walk(walkFn)
}
// checkAndApplyOverrides checks if target has any data that needs to be overridden.
//
// If this function returns true, then the whole vertex should be skipped and
// not executed.
//
// The logic for a vertex is that if it is within an overridden module then we
// don't want to execute it. Instead, we want to just set the values on the
// output nodes for that module directly. So if a node is a
// GraphNodeModuleInstance we want to skip it if there is an entry in our
// overrides data structure that either matches the module for the vertex or
// is a parent of the module for the vertex.
//
// We also want to actually set the new values for any outputs, resources or
// data sources we encounter that should be overridden.
func (g *Graph) checkAndApplyOverrides(overrides *mocking.Overrides, target dag.Vertex) bool {
if overrides.Empty() {
return false
}
switch v := target.(type) {
case GraphNodeOverridable:
// For resource and data sources, we want to skip them completely if
// they are within an overridden module.
resourceInstance := v.ResourceInstanceAddr()
if overrides.IsOverridden(resourceInstance.Module) {
return true
}
if override, ok := overrides.GetOverrideInclProviders(resourceInstance, v.ConfigProvider()); ok {
v.SetOverride(override)
return false
}
if override, ok := overrides.GetOverrideInclProviders(resourceInstance.ContainingResource(), v.ConfigProvider()); ok {
v.SetOverride(override)
return false
}
case *NodeApplyableOutput:
// For outputs, we want to skip them completely if they are deeply
// nested within an overridden module.
module := v.Path()
if overrides.IsDeeplyOverridden(module) {
// If the output is deeply nested under an overridden module we want
// to skip
return true
}
setOverride := func(values cty.Value) {
key := v.Addr.OutputValue.Name
if values.Type().HasAttribute(key) {
v.override = values.GetAttr(key)
} else {
// If we don't have a value provided for an output, then we'll
// just set it to be null.
//
// TODO(liamcervante): Can we generate a value here? Probably
// not as we don't know the type.
v.override = cty.NullVal(cty.DynamicPseudoType)
}
}
// Otherwise, if we are in a directly overridden module then we want to
// apply the overridden output values.
if override, ok := overrides.GetOverride(module); ok {
setOverride(override.Values)
return false
}
lastStepInstanced := len(module) > 0 && module[len(module)-1].InstanceKey != addrs.NoKey
if lastStepInstanced {
// Then we could have overridden all the instances of this module.
if override, ok := overrides.GetOverride(module.ContainingModule()); ok {
setOverride(override.Values)
return false
}
}
case GraphNodeModuleInstance:
// Then this node is simply in a module. It might be that this entire
// module has been overridden, in which case this node shouldn't
// execute.
//
// We checked for resources and outputs earlier, so we know this isn't
// anything special.
module := v.Path()
if overrides.IsOverridden(module) {
return true
}
}
return false
}

@ -15,6 +15,7 @@ import (
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/moduletest/mocking"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/provisioners"
@ -43,6 +44,7 @@ type ContextGraphWalker struct {
RootVariableValues InputValues
Config *configs.Config
PlanTimestamp time.Time
Overrides *mocking.Overrides
// This is an output. Do not set this, nor read it while a graph walk
// is in progress.
@ -114,6 +116,7 @@ func (w *ContextGraphWalker) EvalContext() EvalContext {
Evaluator: evaluator,
VariableValues: w.variableValues,
VariableValuesLock: &w.variableValuesLock,
OverrideValues: w.Overrides,
}
return ctx

@ -205,6 +205,9 @@ type NodeApplyableOutput struct {
DestroyApply bool
Planning bool
// override is set by the graph itself, just before this node executes.
override cty.Value
}
var (
@ -322,7 +325,9 @@ func (n *NodeApplyableOutput) Execute(ctx EvalContext, op walkOperation) (diags
// Checks are not evaluated during a destroy. The checks may fail, may not
// be valid, or may not have been registered at all.
if !n.DestroyApply {
// We also don't evaluate checks for overridden outputs. This is because
// any references within the checks will likely not have been created.
if !n.DestroyApply && n.override == cty.NilVal {
checkRuleSeverity := tfdiags.Error
if n.RefreshOnly {
checkRuleSeverity = tfdiags.Warning
@ -342,32 +347,38 @@ func (n *NodeApplyableOutput) Execute(ctx EvalContext, op walkOperation) (diags
// If there was no change recorded, or the recorded change was not wholly
// known, then we need to re-evaluate the output
if !changeRecorded || !val.IsWhollyKnown() {
// This has to run before we have a state lock, since evaluation also
// reads the state
var evalDiags tfdiags.Diagnostics
val, evalDiags = ctx.EvaluateExpr(n.Config.Expr, cty.DynamicPseudoType, nil)
diags = diags.Append(evalDiags)
// We'll handle errors below, after we have loaded the module.
// Outputs don't have a separate mode for validation, so validate
// depends_on expressions here too
diags = diags.Append(validateDependsOn(ctx, n.Config.DependsOn))
// For root module outputs in particular, an output value must be
// statically declared as sensitive in order to dynamically return
// a sensitive result, to help avoid accidental exposure in the state
// of a sensitive value that the user doesn't want to include there.
if n.Addr.Module.IsRoot() {
if !n.Config.Sensitive && marks.Contains(val, marks.Sensitive) {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Output refers to sensitive values",
Detail: `To reduce the risk of accidentally exporting sensitive data that was intended to be only internal, Terraform requires that any root module output containing sensitive data be explicitly marked as sensitive, to confirm your intent.
// First, we check if we have an overridden value. If we do, then we
// use that and we don't try and evaluate the underlying expression.
val = n.override
if val == cty.NilVal {
// This has to run before we have a state lock, since evaluation also
// reads the state
var evalDiags tfdiags.Diagnostics
val, evalDiags = ctx.EvaluateExpr(n.Config.Expr, cty.DynamicPseudoType, nil)
diags = diags.Append(evalDiags)
// We'll handle errors below, after we have loaded the module.
// Outputs don't have a separate mode for validation, so validate
// depends_on expressions here too
diags = diags.Append(validateDependsOn(ctx, n.Config.DependsOn))
// For root module outputs in particular, an output value must be
// statically declared as sensitive in order to dynamically return
// a sensitive result, to help avoid accidental exposure in the state
// of a sensitive value that the user doesn't want to include there.
if n.Addr.Module.IsRoot() {
if !n.Config.Sensitive && marks.Contains(val, marks.Sensitive) {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Output refers to sensitive values",
Detail: `To reduce the risk of accidentally exporting sensitive data that was intended to be only internal, Terraform requires that any root module output containing sensitive data be explicitly marked as sensitive, to confirm your intent.
If you do intend to export this data, annotate the output value as sensitive by adding the following argument:
sensitive = true`,
Subject: n.Config.DeclRange.Ptr(),
})
Subject: n.Config.DeclRange.Ptr(),
})
}
}
}
}

@ -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)
}

@ -122,6 +122,7 @@ var (
_ GraphNodeAttachProvisionerSchema = (*NodeAbstractResourceInstance)(nil)
_ GraphNodeAttachProviderMetaConfigs = (*NodeAbstractResourceInstance)(nil)
_ GraphNodeTargetable = (*NodeAbstractResourceInstance)(nil)
_ GraphNodeOverridable = (*NodeAbstractResourceInstance)(nil)
_ dag.GraphNodeDotter = (*NodeAbstractResourceInstance)(nil)
)

@ -16,6 +16,7 @@ import (
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/moduletest/mocking"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/plans/objchange"
"github.com/hashicorp/terraform/internal/providers"
@ -43,6 +44,9 @@ type NodeAbstractResourceInstance struct {
// During import we may generate configuration for a resource, which needs
// to be stored in the final change.
generatedConfigHCL string
// override is set by the graph itself, just before this node executes.
override *configs.Override
}
// NewNodeAbstractResourceInstance creates an abstract resource instance graph
@ -135,6 +139,16 @@ func (n *NodeAbstractResourceInstance) AttachResourceState(s *states.Resource) {
n.storedProviderConfig = s.ProviderConfig
}
// GraphNodeOverridable
func (n *NodeAbstractResourceInstance) ConfigProvider() addrs.AbsProviderConfig {
return n.ResolvedProvider
}
// GraphNodeOverridable
func (n *NodeAbstractResourceInstance) SetOverride(override *configs.Override) {
n.override = override
}
// readDiff returns the planned change for a particular resource instance
// object.
func (n *NodeAbstractResourceInstance) readDiff(ctx EvalContext, providerSchema providers.ProviderSchema) (*plans.ResourceInstanceChange, error) {
@ -399,39 +413,50 @@ func (n *NodeAbstractResourceInstance) planDestroy(ctx EvalContext, currentState
return plan, diags
}
// Allow the provider to check the destroy plan, and insert any necessary
// private data.
resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{
TypeName: n.Addr.Resource.Resource.Type,
Config: nullVal,
PriorState: unmarkedPriorVal,
ProposedNewState: nullVal,
PriorPrivate: currentState.Private,
ProviderMeta: metaConfigVal,
})
var resp providers.PlanResourceChangeResponse
if n.override != nil {
// If we have an overridden value from the test framework, that means
// this value was created without consulting the provider previously.
// We can just set the planned state to deleted without consulting the
// provider.
resp = providers.PlanResourceChangeResponse{
PlannedState: nullVal,
}
} else {
// Allow the provider to check the destroy plan, and insert any
// necessary private data.
resp = provider.PlanResourceChange(providers.PlanResourceChangeRequest{
TypeName: n.Addr.Resource.Resource.Type,
Config: nullVal,
PriorState: unmarkedPriorVal,
ProposedNewState: nullVal,
PriorPrivate: currentState.Private,
ProviderMeta: metaConfigVal,
})
// We may not have a config for all destroys, but we want to reference it in
// the diagnostics if we do.
if n.Config != nil {
resp.Diagnostics = resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String())
}
diags = diags.Append(resp.Diagnostics)
if diags.HasErrors() {
return plan, diags
}
// We may not have a config for all destroys, but we want to reference
// it in the diagnostics if we do.
if n.Config != nil {
resp.Diagnostics = resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String())
}
diags = diags.Append(resp.Diagnostics)
if diags.HasErrors() {
return plan, diags
}
// Check that the provider returned a null value here, since that is the
// only valid value for a destroy plan.
if !resp.PlannedState.IsNull() {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Provider produced invalid plan",
fmt.Sprintf(
"Provider %q planned a non-null destroy value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
n.ResolvedProvider.Provider, n.Addr),
),
)
return plan, diags
// Check that the provider returned a null value here, since that is the
// only valid value for a destroy plan.
if !resp.PlannedState.IsNull() {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Provider produced invalid plan",
fmt.Sprintf(
"Provider %q planned a non-null destroy value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
n.ResolvedProvider.Provider, n.Addr),
),
)
return plan, diags
}
}
// Plan is always the same for a destroy.
@ -563,14 +588,21 @@ func (n *NodeAbstractResourceInstance) refresh(ctx EvalContext, deposedKey state
priorVal, priorPaths = priorVal.UnmarkDeepWithPaths()
}
providerReq := providers.ReadResourceRequest{
TypeName: n.Addr.Resource.Resource.Type,
PriorState: priorVal,
Private: state.Private,
ProviderMeta: metaConfigVal,
var resp providers.ReadResourceResponse
if n.override != nil {
// If we have an override set for this resource, we don't want to talk
// to the provider so we'll just return whatever was in state.
resp = providers.ReadResourceResponse{
NewState: priorVal,
}
} else {
resp = provider.ReadResource(providers.ReadResourceRequest{
TypeName: n.Addr.Resource.Resource.Type,
PriorState: priorVal,
Private: state.Private,
ProviderMeta: metaConfigVal,
})
}
resp := provider.ReadResource(providerReq)
if n.Config != nil {
resp.Diagnostics = resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String())
}
@ -798,14 +830,36 @@ func (n *NodeAbstractResourceInstance) plan(
return nil, nil, keyData, diags
}
resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{
TypeName: n.Addr.Resource.Resource.Type,
Config: unmarkedConfigVal,
PriorState: unmarkedPriorVal,
ProposedNewState: proposedNewVal,
PriorPrivate: priorPrivate,
ProviderMeta: metaConfigVal,
})
var resp providers.PlanResourceChangeResponse
if n.override != nil {
// Then we have an override to apply for this change. But, overrides
// only matter when we are creating a resource for the first time as we
// only apply computed values.
if priorVal.IsNull() {
// Then we are actually creating something, so let's populate the
// computed values from our override value.
override, overrideDiags := mocking.PlanComputedValuesForResource(proposedNewVal, schema)
resp = providers.PlanResourceChangeResponse{
PlannedState: override,
Diagnostics: overrideDiags,
}
} else {
// This is an update operation, and we don't actually have any
// computed values that need to be applied.
resp = providers.PlanResourceChangeResponse{
PlannedState: proposedNewVal,
}
}
} else {
resp = provider.PlanResourceChange(providers.PlanResourceChangeRequest{
TypeName: n.Addr.Resource.Resource.Type,
Config: unmarkedConfigVal,
PriorState: unmarkedPriorVal,
ProposedNewState: proposedNewVal,
PriorPrivate: priorPrivate,
ProviderMeta: metaConfigVal,
})
}
diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config, n.Addr.String()))
if diags.HasErrors() {
return nil, nil, keyData, diags
@ -1032,14 +1086,24 @@ func (n *NodeAbstractResourceInstance) plan(
// create a new proposed value from the null state and the config
proposedNewVal = objchange.ProposedNew(schema, nullPriorVal, unmarkedConfigVal)
resp = provider.PlanResourceChange(providers.PlanResourceChangeRequest{
TypeName: n.Addr.Resource.Resource.Type,
Config: unmarkedConfigVal,
PriorState: nullPriorVal,
ProposedNewState: proposedNewVal,
PriorPrivate: plannedPrivate,
ProviderMeta: metaConfigVal,
})
if n.override != nil {
// In this case, we are always creating the resource so we don't
// do any validation, and just call out to the mocking library.
override, overrideDiags := mocking.PlanComputedValuesForResource(proposedNewVal, schema)
resp = providers.PlanResourceChangeResponse{
PlannedState: override,
Diagnostics: overrideDiags,
}
} else {
resp = provider.PlanResourceChange(providers.PlanResourceChangeRequest{
TypeName: n.Addr.Resource.Resource.Type,
Config: unmarkedConfigVal,
PriorState: nullPriorVal,
ProposedNewState: proposedNewVal,
PriorPrivate: plannedPrivate,
ProviderMeta: metaConfigVal,
})
}
// We need to tread carefully here, since if there are any warnings
// in here they probably also came out of our previous call to
// PlanResourceChange above, and so we don't want to repeat them.
@ -1447,11 +1511,23 @@ func (n *NodeAbstractResourceInstance) readDataSource(ctx EvalContext, configVal
return newVal, diags
}
resp := provider.ReadDataSource(providers.ReadDataSourceRequest{
TypeName: n.Addr.ContainingResource().Resource.Type,
Config: configVal,
ProviderMeta: metaConfigVal,
})
var resp providers.ReadDataSourceResponse
if n.override != nil {
override, overrideDiags := mocking.ComputedValuesForDataSource(configVal, mocking.ReplacementValue{
Value: n.override.Values,
Range: n.override.ValuesRange,
}, schema)
resp = providers.ReadDataSourceResponse{
State: override,
Diagnostics: overrideDiags,
}
} else {
resp = provider.ReadDataSource(providers.ReadDataSourceRequest{
TypeName: n.Addr.ContainingResource().Resource.Type,
Config: configVal,
ProviderMeta: metaConfigVal,
})
}
diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config, n.Addr.String()))
if diags.HasErrors() {
return newVal, diags
@ -2293,14 +2369,35 @@ func (n *NodeAbstractResourceInstance) apply(
return newState, diags
}
resp := provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{
TypeName: n.Addr.Resource.Resource.Type,
PriorState: unmarkedBefore,
Config: unmarkedConfigVal,
PlannedState: unmarkedAfter,
PlannedPrivate: change.Private,
ProviderMeta: metaConfigVal,
})
var resp providers.ApplyResourceChangeResponse
if n.override != nil {
// As with the planning stage, we only need to worry about computed
// values the first time the object is created. Otherwise, we're happy
// to just apply whatever the user asked for.
if change.Action == plans.Create {
override, overrideDiags := mocking.ApplyComputedValuesForResource(unmarkedAfter, mocking.ReplacementValue{
Value: n.override.Values,
Range: n.override.ValuesRange,
}, schema)
resp = providers.ApplyResourceChangeResponse{
NewState: override,
Diagnostics: overrideDiags,
}
} else {
resp = providers.ApplyResourceChangeResponse{
NewState: unmarkedAfter,
}
}
} else {
resp = provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{
TypeName: n.Addr.Resource.Resource.Type,
PriorState: unmarkedBefore,
Config: unmarkedConfigVal,
PlannedState: unmarkedAfter,
PlannedPrivate: change.Private,
ProviderMeta: metaConfigVal,
})
}
applyDiags := resp.Diagnostics
if applyConfig != nil {
applyDiags = applyDiags.InConfigBody(applyConfig.Config, n.Addr.String())

@ -241,6 +241,14 @@ func mustReference(s string) *addrs.Reference {
return p
}
func mustModuleInstance(s string) addrs.ModuleInstance {
p, diags := addrs.ParseModuleInstanceStr(s)
if diags.HasErrors() {
panic(diags.Err())
}
return p
}
// HookRecordApplyOrder is a test hook that records the order of applies
// by recording the PreApply event.
type HookRecordApplyOrder struct {

Loading…
Cancel
Save