github.com/opentofu/opentofu@v1.7.1/internal/tofu/graph_builder_plan_test.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package tofu 7 8 import ( 9 "strings" 10 "testing" 11 12 "github.com/google/go-cmp/cmp" 13 "github.com/zclconf/go-cty/cty" 14 15 "github.com/opentofu/opentofu/internal/addrs" 16 "github.com/opentofu/opentofu/internal/configs/configschema" 17 "github.com/opentofu/opentofu/internal/providers" 18 ) 19 20 func TestPlanGraphBuilder_impl(t *testing.T) { 21 var _ GraphBuilder = new(PlanGraphBuilder) 22 } 23 24 func TestPlanGraphBuilder(t *testing.T) { 25 awsProvider := &MockProvider{ 26 GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ 27 Provider: providers.Schema{Block: simpleTestSchema()}, 28 ResourceTypes: map[string]providers.Schema{ 29 "aws_security_group": {Block: simpleTestSchema()}, 30 "aws_instance": {Block: simpleTestSchema()}, 31 "aws_load_balancer": {Block: simpleTestSchema()}, 32 }, 33 }, 34 } 35 openstackProvider := mockProviderWithResourceTypeSchema("openstack_floating_ip", simpleTestSchema()) 36 plugins := newContextPluginsForTest(map[addrs.Provider]providers.Factory{ 37 addrs.NewDefaultProvider("aws"): providers.FactoryFixed(awsProvider), 38 addrs.NewDefaultProvider("openstack"): providers.FactoryFixed(openstackProvider), 39 }, t) 40 41 b := &PlanGraphBuilder{ 42 Config: testModule(t, "graph-builder-plan-basic"), 43 Plugins: plugins, 44 Operation: walkPlan, 45 } 46 47 g, err := b.Build(addrs.RootModuleInstance) 48 if err != nil { 49 t.Fatalf("err: %s", err) 50 } 51 52 if g.Path.String() != addrs.RootModuleInstance.String() { 53 t.Fatalf("wrong module path %q", g.Path) 54 } 55 56 got := strings.TrimSpace(g.String()) 57 want := strings.TrimSpace(testPlanGraphBuilderStr) 58 if diff := cmp.Diff(want, got); diff != "" { 59 t.Fatalf("wrong result\n%s", diff) 60 } 61 } 62 63 func TestPlanGraphBuilder_dynamicBlock(t *testing.T) { 64 provider := mockProviderWithResourceTypeSchema("test_thing", &configschema.Block{ 65 Attributes: map[string]*configschema.Attribute{ 66 "id": {Type: cty.String, Computed: true}, 67 "list": {Type: cty.List(cty.String), Computed: true}, 68 }, 69 BlockTypes: map[string]*configschema.NestedBlock{ 70 "nested": { 71 Nesting: configschema.NestingList, 72 Block: configschema.Block{ 73 Attributes: map[string]*configschema.Attribute{ 74 "foo": {Type: cty.String, Optional: true}, 75 }, 76 }, 77 }, 78 }, 79 }) 80 plugins := newContextPluginsForTest(map[addrs.Provider]providers.Factory{ 81 addrs.NewDefaultProvider("test"): providers.FactoryFixed(provider), 82 }, t) 83 84 b := &PlanGraphBuilder{ 85 Config: testModule(t, "graph-builder-plan-dynblock"), 86 Plugins: plugins, 87 Operation: walkPlan, 88 } 89 90 g, err := b.Build(addrs.RootModuleInstance) 91 if err != nil { 92 t.Fatalf("err: %s", err) 93 } 94 95 if g.Path.String() != addrs.RootModuleInstance.String() { 96 t.Fatalf("wrong module path %q", g.Path) 97 } 98 99 // This test is here to make sure we properly detect references inside 100 // the special "dynamic" block construct. The most important thing here 101 // is that at the end test_thing.c depends on both test_thing.a and 102 // test_thing.b. Other details might shift over time as other logic in 103 // the graph builders changes. 104 got := strings.TrimSpace(g.String()) 105 want := strings.TrimSpace(` 106 provider["registry.opentofu.org/hashicorp/test"] 107 provider["registry.opentofu.org/hashicorp/test"] (close) 108 test_thing.c (expand) 109 root 110 provider["registry.opentofu.org/hashicorp/test"] (close) 111 test_thing.a (expand) 112 provider["registry.opentofu.org/hashicorp/test"] 113 test_thing.b (expand) 114 provider["registry.opentofu.org/hashicorp/test"] 115 test_thing.c (expand) 116 test_thing.a (expand) 117 test_thing.b (expand) 118 `) 119 if diff := cmp.Diff(want, got); diff != "" { 120 t.Fatalf("wrong result\n%s", diff) 121 } 122 } 123 124 func TestPlanGraphBuilder_attrAsBlocks(t *testing.T) { 125 provider := mockProviderWithResourceTypeSchema("test_thing", &configschema.Block{ 126 Attributes: map[string]*configschema.Attribute{ 127 "id": {Type: cty.String, Computed: true}, 128 "nested": { 129 Type: cty.List(cty.Object(map[string]cty.Type{ 130 "foo": cty.String, 131 })), 132 Optional: true, 133 }, 134 }, 135 }) 136 plugins := newContextPluginsForTest(map[addrs.Provider]providers.Factory{ 137 addrs.NewDefaultProvider("test"): providers.FactoryFixed(provider), 138 }, t) 139 140 b := &PlanGraphBuilder{ 141 Config: testModule(t, "graph-builder-plan-attr-as-blocks"), 142 Plugins: plugins, 143 Operation: walkPlan, 144 } 145 146 g, err := b.Build(addrs.RootModuleInstance) 147 if err != nil { 148 t.Fatalf("err: %s", err) 149 } 150 151 if g.Path.String() != addrs.RootModuleInstance.String() { 152 t.Fatalf("wrong module path %q", g.Path) 153 } 154 155 // This test is here to make sure we properly detect references inside 156 // the "nested" block that is actually defined in the schema as a 157 // list-of-objects attribute. This requires some special effort 158 // inside lang.ReferencesInBlock to make sure it searches blocks of 159 // type "nested" along with an attribute named "nested". 160 got := strings.TrimSpace(g.String()) 161 want := strings.TrimSpace(` 162 provider["registry.opentofu.org/hashicorp/test"] 163 provider["registry.opentofu.org/hashicorp/test"] (close) 164 test_thing.b (expand) 165 root 166 provider["registry.opentofu.org/hashicorp/test"] (close) 167 test_thing.a (expand) 168 provider["registry.opentofu.org/hashicorp/test"] 169 test_thing.b (expand) 170 test_thing.a (expand) 171 `) 172 if diff := cmp.Diff(want, got); diff != "" { 173 t.Fatalf("wrong result\n%s", diff) 174 } 175 } 176 177 func TestPlanGraphBuilder_targetModule(t *testing.T) { 178 b := &PlanGraphBuilder{ 179 Config: testModule(t, "graph-builder-plan-target-module-provider"), 180 Plugins: simpleMockPluginLibrary(), 181 Targets: []addrs.Targetable{ 182 addrs.RootModuleInstance.Child("child2", addrs.NoKey), 183 }, 184 Operation: walkPlan, 185 } 186 187 g, err := b.Build(addrs.RootModuleInstance) 188 if err != nil { 189 t.Fatalf("err: %s", err) 190 } 191 192 t.Logf("Graph: %s", g.String()) 193 194 testGraphNotContains(t, g, `module.child1.provider["registry.opentofu.org/hashicorp/test"]`) 195 testGraphNotContains(t, g, "module.child1.test_object.foo") 196 } 197 198 func TestPlanGraphBuilder_forEach(t *testing.T) { 199 awsProvider := mockProviderWithResourceTypeSchema("aws_instance", simpleTestSchema()) 200 201 plugins := newContextPluginsForTest(map[addrs.Provider]providers.Factory{ 202 addrs.NewDefaultProvider("aws"): providers.FactoryFixed(awsProvider), 203 }, t) 204 205 b := &PlanGraphBuilder{ 206 Config: testModule(t, "plan-for-each"), 207 Plugins: plugins, 208 Operation: walkPlan, 209 } 210 211 g, err := b.Build(addrs.RootModuleInstance) 212 if err != nil { 213 t.Fatalf("err: %s", err) 214 } 215 216 if g.Path.String() != addrs.RootModuleInstance.String() { 217 t.Fatalf("wrong module path %q", g.Path) 218 } 219 220 got := strings.TrimSpace(g.String()) 221 // We're especially looking for the edge here, where aws_instance.bat 222 // has a dependency on aws_instance.boo 223 want := strings.TrimSpace(testPlanGraphBuilderForEachStr) 224 if diff := cmp.Diff(want, got); diff != "" { 225 t.Fatalf("wrong result\n%s", diff) 226 } 227 } 228 229 const testPlanGraphBuilderStr = ` 230 aws_instance.web (expand) 231 aws_security_group.firewall (expand) 232 var.foo 233 aws_load_balancer.weblb (expand) 234 aws_instance.web (expand) 235 aws_security_group.firewall (expand) 236 provider["registry.opentofu.org/hashicorp/aws"] 237 local.instance_id (expand) 238 aws_instance.web (expand) 239 openstack_floating_ip.random (expand) 240 provider["registry.opentofu.org/hashicorp/openstack"] 241 output.instance_id (expand) 242 local.instance_id (expand) 243 provider["registry.opentofu.org/hashicorp/aws"] 244 openstack_floating_ip.random (expand) 245 provider["registry.opentofu.org/hashicorp/aws"] (close) 246 aws_load_balancer.weblb (expand) 247 provider["registry.opentofu.org/hashicorp/openstack"] 248 provider["registry.opentofu.org/hashicorp/openstack"] (close) 249 openstack_floating_ip.random (expand) 250 root 251 output.instance_id (expand) 252 provider["registry.opentofu.org/hashicorp/aws"] (close) 253 provider["registry.opentofu.org/hashicorp/openstack"] (close) 254 var.foo 255 ` 256 const testPlanGraphBuilderForEachStr = ` 257 aws_instance.bar (expand) 258 provider["registry.opentofu.org/hashicorp/aws"] 259 aws_instance.bar2 (expand) 260 provider["registry.opentofu.org/hashicorp/aws"] 261 aws_instance.bat (expand) 262 aws_instance.boo (expand) 263 aws_instance.baz (expand) 264 provider["registry.opentofu.org/hashicorp/aws"] 265 aws_instance.boo (expand) 266 provider["registry.opentofu.org/hashicorp/aws"] 267 aws_instance.foo (expand) 268 provider["registry.opentofu.org/hashicorp/aws"] 269 provider["registry.opentofu.org/hashicorp/aws"] 270 provider["registry.opentofu.org/hashicorp/aws"] (close) 271 aws_instance.bar (expand) 272 aws_instance.bar2 (expand) 273 aws_instance.bat (expand) 274 aws_instance.baz (expand) 275 aws_instance.foo (expand) 276 root 277 provider["registry.opentofu.org/hashicorp/aws"] (close) 278 `