opentofu/internal/terraform/graph_builder_plan_test.go
Martin Atkins 3cd069900b core: Use DynamicExpand even for root module outputs
We previously had a special case in the graph transformer for output
values where it would directly create an individual output value node
instead of an "expand" node as we would do for output values in nested
modules.

While it's true that we do always know that expanding a root module
output value will always produce exactly one instance, treating this case
as special creates the risk of those two codepaths diverging in other
ways.

Instead, we'll let the expand node also deal with root modules and
minimize the special case only to how we look up any changes for the
output values, since the design of plans.Changes is a bit awkward and
requires us to ask the question differently for root module output values.
Otherwise, the behavior will now be consistent across all output values
regardless of module.
2022-07-22 15:25:22 -07:00

274 lines
8.3 KiB
Go

package terraform
import (
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/providers"
)
func TestPlanGraphBuilder_impl(t *testing.T) {
var _ GraphBuilder = new(PlanGraphBuilder)
}
func TestPlanGraphBuilder(t *testing.T) {
awsProvider := &MockProvider{
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
Provider: providers.Schema{Block: simpleTestSchema()},
ResourceTypes: map[string]providers.Schema{
"aws_security_group": {Block: simpleTestSchema()},
"aws_instance": {Block: simpleTestSchema()},
"aws_load_balancer": {Block: simpleTestSchema()},
},
},
}
openstackProvider := mockProviderWithResourceTypeSchema("openstack_floating_ip", simpleTestSchema())
plugins := newContextPlugins(map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): providers.FactoryFixed(awsProvider),
addrs.NewDefaultProvider("openstack"): providers.FactoryFixed(openstackProvider),
}, nil)
b := &PlanGraphBuilder{
Config: testModule(t, "graph-builder-plan-basic"),
Plugins: plugins,
Operation: walkPlan,
}
g, err := b.Build(addrs.RootModuleInstance)
if err != nil {
t.Fatalf("err: %s", err)
}
if g.Path.String() != addrs.RootModuleInstance.String() {
t.Fatalf("wrong module path %q", g.Path)
}
got := strings.TrimSpace(g.String())
want := strings.TrimSpace(testPlanGraphBuilderStr)
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("wrong result\n%s", diff)
}
}
func TestPlanGraphBuilder_dynamicBlock(t *testing.T) {
provider := mockProviderWithResourceTypeSchema("test_thing", &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Computed: true},
"list": {Type: cty.List(cty.String), Computed: true},
},
BlockTypes: map[string]*configschema.NestedBlock{
"nested": {
Nesting: configschema.NestingList,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {Type: cty.String, Optional: true},
},
},
},
},
})
plugins := newContextPlugins(map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): providers.FactoryFixed(provider),
}, nil)
b := &PlanGraphBuilder{
Config: testModule(t, "graph-builder-plan-dynblock"),
Plugins: plugins,
Operation: walkPlan,
}
g, err := b.Build(addrs.RootModuleInstance)
if err != nil {
t.Fatalf("err: %s", err)
}
if g.Path.String() != addrs.RootModuleInstance.String() {
t.Fatalf("wrong module path %q", g.Path)
}
// This test is here to make sure we properly detect references inside
// the special "dynamic" block construct. The most important thing here
// is that at the end test_thing.c depends on both test_thing.a and
// test_thing.b. Other details might shift over time as other logic in
// the graph builders changes.
got := strings.TrimSpace(g.String())
want := strings.TrimSpace(`
provider["registry.terraform.io/hashicorp/test"]
provider["registry.terraform.io/hashicorp/test"] (close)
test_thing.c (expand)
root
provider["registry.terraform.io/hashicorp/test"] (close)
test_thing.a (expand)
provider["registry.terraform.io/hashicorp/test"]
test_thing.b (expand)
provider["registry.terraform.io/hashicorp/test"]
test_thing.c (expand)
test_thing.a (expand)
test_thing.b (expand)
`)
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("wrong result\n%s", diff)
}
}
func TestPlanGraphBuilder_attrAsBlocks(t *testing.T) {
provider := mockProviderWithResourceTypeSchema("test_thing", &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Computed: true},
"nested": {
Type: cty.List(cty.Object(map[string]cty.Type{
"foo": cty.String,
})),
Optional: true,
},
},
})
plugins := newContextPlugins(map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): providers.FactoryFixed(provider),
}, nil)
b := &PlanGraphBuilder{
Config: testModule(t, "graph-builder-plan-attr-as-blocks"),
Plugins: plugins,
Operation: walkPlan,
}
g, err := b.Build(addrs.RootModuleInstance)
if err != nil {
t.Fatalf("err: %s", err)
}
if g.Path.String() != addrs.RootModuleInstance.String() {
t.Fatalf("wrong module path %q", g.Path)
}
// This test is here to make sure we properly detect references inside
// the "nested" block that is actually defined in the schema as a
// list-of-objects attribute. This requires some special effort
// inside lang.ReferencesInBlock to make sure it searches blocks of
// type "nested" along with an attribute named "nested".
got := strings.TrimSpace(g.String())
want := strings.TrimSpace(`
provider["registry.terraform.io/hashicorp/test"]
provider["registry.terraform.io/hashicorp/test"] (close)
test_thing.b (expand)
root
provider["registry.terraform.io/hashicorp/test"] (close)
test_thing.a (expand)
provider["registry.terraform.io/hashicorp/test"]
test_thing.b (expand)
test_thing.a (expand)
`)
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("wrong result\n%s", diff)
}
}
func TestPlanGraphBuilder_targetModule(t *testing.T) {
b := &PlanGraphBuilder{
Config: testModule(t, "graph-builder-plan-target-module-provider"),
Plugins: simpleMockPluginLibrary(),
Targets: []addrs.Targetable{
addrs.RootModuleInstance.Child("child2", addrs.NoKey),
},
Operation: walkPlan,
}
g, err := b.Build(addrs.RootModuleInstance)
if err != nil {
t.Fatalf("err: %s", err)
}
t.Logf("Graph: %s", g.String())
testGraphNotContains(t, g, `module.child1.provider["registry.terraform.io/hashicorp/test"]`)
testGraphNotContains(t, g, "module.child1.test_object.foo")
}
func TestPlanGraphBuilder_forEach(t *testing.T) {
awsProvider := mockProviderWithResourceTypeSchema("aws_instance", simpleTestSchema())
plugins := newContextPlugins(map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): providers.FactoryFixed(awsProvider),
}, nil)
b := &PlanGraphBuilder{
Config: testModule(t, "plan-for-each"),
Plugins: plugins,
Operation: walkPlan,
}
g, err := b.Build(addrs.RootModuleInstance)
if err != nil {
t.Fatalf("err: %s", err)
}
if g.Path.String() != addrs.RootModuleInstance.String() {
t.Fatalf("wrong module path %q", g.Path)
}
got := strings.TrimSpace(g.String())
// We're especially looking for the edge here, where aws_instance.bat
// has a dependency on aws_instance.boo
want := strings.TrimSpace(testPlanGraphBuilderForEachStr)
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("wrong result\n%s", diff)
}
}
const testPlanGraphBuilderStr = `
aws_instance.web (expand)
aws_security_group.firewall (expand)
var.foo
aws_load_balancer.weblb (expand)
aws_instance.web (expand)
aws_security_group.firewall (expand)
provider["registry.terraform.io/hashicorp/aws"]
local.instance_id (expand)
aws_instance.web (expand)
openstack_floating_ip.random (expand)
provider["registry.terraform.io/hashicorp/openstack"]
output.instance_id (expand)
local.instance_id (expand)
provider["registry.terraform.io/hashicorp/aws"]
openstack_floating_ip.random (expand)
provider["registry.terraform.io/hashicorp/aws"] (close)
aws_load_balancer.weblb (expand)
provider["registry.terraform.io/hashicorp/openstack"]
provider["registry.terraform.io/hashicorp/openstack"] (close)
openstack_floating_ip.random (expand)
root
output.instance_id (expand)
provider["registry.terraform.io/hashicorp/aws"] (close)
provider["registry.terraform.io/hashicorp/openstack"] (close)
var.foo
`
const testPlanGraphBuilderForEachStr = `
aws_instance.bar (expand)
provider["registry.terraform.io/hashicorp/aws"]
aws_instance.bar2 (expand)
provider["registry.terraform.io/hashicorp/aws"]
aws_instance.bat (expand)
aws_instance.boo (expand)
aws_instance.baz (expand)
provider["registry.terraform.io/hashicorp/aws"]
aws_instance.boo (expand)
provider["registry.terraform.io/hashicorp/aws"]
aws_instance.foo (expand)
provider["registry.terraform.io/hashicorp/aws"]
provider["registry.terraform.io/hashicorp/aws"]
provider["registry.terraform.io/hashicorp/aws"] (close)
aws_instance.bar (expand)
aws_instance.bar2 (expand)
aws_instance.bat (expand)
aws_instance.baz (expand)
aws_instance.foo (expand)
root
provider["registry.terraform.io/hashicorp/aws"] (close)
`