github.com/opentofu/opentofu@v1.7.1/internal/tofu/transform_provider_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 "fmt" 10 "strings" 11 "testing" 12 13 "github.com/opentofu/opentofu/internal/addrs" 14 "github.com/opentofu/opentofu/internal/configs" 15 "github.com/opentofu/opentofu/internal/dag" 16 ) 17 18 func testProviderTransformerGraph(t *testing.T, cfg *configs.Config) *Graph { 19 t.Helper() 20 21 g := &Graph{Path: addrs.RootModuleInstance} 22 ct := &ConfigTransformer{Config: cfg} 23 if err := ct.Transform(g); err != nil { 24 t.Fatal(err) 25 } 26 arct := &AttachResourceConfigTransformer{Config: cfg} 27 if err := arct.Transform(g); err != nil { 28 t.Fatal(err) 29 } 30 31 return g 32 } 33 34 // This variant exists purely for testing and can not currently include the ProviderFunctionTransformer 35 func testTransformProviders(concrete ConcreteProviderNodeFunc, config *configs.Config) GraphTransformer { 36 return GraphTransformMulti( 37 // Add providers from the config 38 &ProviderConfigTransformer{ 39 Config: config, 40 Concrete: concrete, 41 }, 42 // Add any remaining missing providers 43 &MissingProviderTransformer{ 44 Config: config, 45 Concrete: concrete, 46 }, 47 // Connect the providers 48 &ProviderTransformer{ 49 Config: config, 50 }, 51 // After schema transformer, we can add function references 52 // &ProviderFunctionTransformer{Config: config}, 53 // Remove unused providers and proxies 54 &PruneProviderTransformer{}, 55 ) 56 } 57 58 func TestProviderTransformer(t *testing.T) { 59 mod := testModule(t, "transform-provider-basic") 60 61 g := testProviderTransformerGraph(t, mod) 62 { 63 transform := &MissingProviderTransformer{} 64 if err := transform.Transform(g); err != nil { 65 t.Fatalf("err: %s", err) 66 } 67 } 68 69 transform := &ProviderTransformer{} 70 if err := transform.Transform(g); err != nil { 71 t.Fatalf("err: %s", err) 72 } 73 74 actual := strings.TrimSpace(g.String()) 75 expected := strings.TrimSpace(testTransformProviderBasicStr) 76 if actual != expected { 77 t.Fatalf("bad:\n\n%s", actual) 78 } 79 } 80 81 // Test providers with FQNs that do not match the typeName 82 func TestProviderTransformer_fqns(t *testing.T) { 83 for _, mod := range []string{"fqns", "fqns-module"} { 84 mod := testModule(t, fmt.Sprintf("transform-provider-%s", mod)) 85 86 g := testProviderTransformerGraph(t, mod) 87 { 88 transform := &MissingProviderTransformer{Config: mod} 89 if err := transform.Transform(g); err != nil { 90 t.Fatalf("err: %s", err) 91 } 92 } 93 94 transform := &ProviderTransformer{Config: mod} 95 if err := transform.Transform(g); err != nil { 96 t.Fatalf("err: %s", err) 97 } 98 99 actual := strings.TrimSpace(g.String()) 100 expected := strings.TrimSpace(testTransformProviderBasicStr) 101 if actual != expected { 102 t.Fatalf("bad:\n\n%s", actual) 103 } 104 } 105 } 106 107 func TestCloseProviderTransformer(t *testing.T) { 108 mod := testModule(t, "transform-provider-basic") 109 g := testProviderTransformerGraph(t, mod) 110 111 { 112 transform := &MissingProviderTransformer{} 113 if err := transform.Transform(g); err != nil { 114 t.Fatalf("err: %s", err) 115 } 116 } 117 118 { 119 transform := &ProviderTransformer{} 120 if err := transform.Transform(g); err != nil { 121 t.Fatalf("err: %s", err) 122 } 123 } 124 125 { 126 transform := &CloseProviderTransformer{} 127 if err := transform.Transform(g); err != nil { 128 t.Fatalf("err: %s", err) 129 } 130 } 131 132 actual := strings.TrimSpace(g.String()) 133 expected := strings.TrimSpace(testTransformCloseProviderBasicStr) 134 if actual != expected { 135 t.Fatalf("bad:\n\n%s", actual) 136 } 137 } 138 139 func TestCloseProviderTransformer_withTargets(t *testing.T) { 140 mod := testModule(t, "transform-provider-basic") 141 142 g := testProviderTransformerGraph(t, mod) 143 transforms := []GraphTransformer{ 144 &MissingProviderTransformer{}, 145 &ProviderTransformer{}, 146 &CloseProviderTransformer{}, 147 &TargetsTransformer{ 148 Targets: []addrs.Targetable{ 149 addrs.RootModuleInstance.Resource( 150 addrs.ManagedResourceMode, "something", "else", 151 ), 152 }, 153 }, 154 } 155 156 for _, tr := range transforms { 157 if err := tr.Transform(g); err != nil { 158 t.Fatalf("err: %s", err) 159 } 160 } 161 162 actual := strings.TrimSpace(g.String()) 163 expected := strings.TrimSpace(``) 164 if actual != expected { 165 t.Fatalf("expected:%s\n\ngot:\n\n%s", expected, actual) 166 } 167 } 168 169 func TestMissingProviderTransformer(t *testing.T) { 170 mod := testModule(t, "transform-provider-missing") 171 172 g := testProviderTransformerGraph(t, mod) 173 { 174 transform := &MissingProviderTransformer{} 175 if err := transform.Transform(g); err != nil { 176 t.Fatalf("err: %s", err) 177 } 178 } 179 180 { 181 transform := &ProviderTransformer{} 182 if err := transform.Transform(g); err != nil { 183 t.Fatalf("err: %s", err) 184 } 185 } 186 187 { 188 transform := &CloseProviderTransformer{} 189 if err := transform.Transform(g); err != nil { 190 t.Fatalf("err: %s", err) 191 } 192 } 193 194 actual := strings.TrimSpace(g.String()) 195 expected := strings.TrimSpace(testTransformMissingProviderBasicStr) 196 if actual != expected { 197 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 198 } 199 } 200 201 func TestMissingProviderTransformer_grandchildMissing(t *testing.T) { 202 mod := testModule(t, "transform-provider-missing-grandchild") 203 204 concrete := func(a *NodeAbstractProvider) dag.Vertex { return a } 205 206 g := testProviderTransformerGraph(t, mod) 207 { 208 transform := testTransformProviders(concrete, mod) 209 if err := transform.Transform(g); err != nil { 210 t.Fatalf("err: %s", err) 211 } 212 } 213 { 214 transform := &TransitiveReductionTransformer{} 215 if err := transform.Transform(g); err != nil { 216 t.Fatalf("err: %s", err) 217 } 218 } 219 220 actual := strings.TrimSpace(g.String()) 221 expected := strings.TrimSpace(testTransformMissingGrandchildProviderStr) 222 if actual != expected { 223 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 224 } 225 } 226 227 func TestPruneProviderTransformer(t *testing.T) { 228 mod := testModule(t, "transform-provider-prune") 229 230 g := testProviderTransformerGraph(t, mod) 231 { 232 transform := &MissingProviderTransformer{} 233 if err := transform.Transform(g); err != nil { 234 t.Fatalf("err: %s", err) 235 } 236 } 237 238 { 239 transform := &ProviderTransformer{} 240 if err := transform.Transform(g); err != nil { 241 t.Fatalf("err: %s", err) 242 } 243 } 244 245 { 246 transform := &CloseProviderTransformer{} 247 if err := transform.Transform(g); err != nil { 248 t.Fatalf("err: %s", err) 249 } 250 } 251 252 { 253 transform := &PruneProviderTransformer{} 254 if err := transform.Transform(g); err != nil { 255 t.Fatalf("err: %s", err) 256 } 257 } 258 259 actual := strings.TrimSpace(g.String()) 260 expected := strings.TrimSpace(testTransformPruneProviderBasicStr) 261 if actual != expected { 262 t.Fatalf("bad:\n\n%s", actual) 263 } 264 } 265 266 // the child module resource is attached to the configured parent provider 267 func TestProviderConfigTransformer_parentProviders(t *testing.T) { 268 mod := testModule(t, "transform-provider-inherit") 269 concrete := func(a *NodeAbstractProvider) dag.Vertex { return a } 270 271 g := testProviderTransformerGraph(t, mod) 272 { 273 tf := testTransformProviders(concrete, mod) 274 if err := tf.Transform(g); err != nil { 275 t.Fatalf("err: %s", err) 276 } 277 } 278 279 actual := strings.TrimSpace(g.String()) 280 expected := strings.TrimSpace(testTransformModuleProviderConfigStr) 281 if actual != expected { 282 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 283 } 284 } 285 286 // the child module resource is attached to the configured grand-parent provider 287 func TestProviderConfigTransformer_grandparentProviders(t *testing.T) { 288 mod := testModule(t, "transform-provider-grandchild-inherit") 289 concrete := func(a *NodeAbstractProvider) dag.Vertex { return a } 290 291 g := testProviderTransformerGraph(t, mod) 292 { 293 tf := testTransformProviders(concrete, mod) 294 if err := tf.Transform(g); err != nil { 295 t.Fatalf("err: %s", err) 296 } 297 } 298 299 actual := strings.TrimSpace(g.String()) 300 expected := strings.TrimSpace(testTransformModuleProviderGrandparentStr) 301 if actual != expected { 302 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 303 } 304 } 305 306 func TestProviderConfigTransformer_inheritOldSkool(t *testing.T) { 307 mod := testModuleInline(t, map[string]string{ 308 "main.tf": ` 309 provider "test" { 310 test_string = "config" 311 } 312 313 module "moda" { 314 source = "./moda" 315 } 316 `, 317 318 "moda/main.tf": ` 319 resource "test_object" "a" { 320 } 321 `, 322 }) 323 concrete := func(a *NodeAbstractProvider) dag.Vertex { return a } 324 325 g := testProviderTransformerGraph(t, mod) 326 { 327 tf := testTransformProviders(concrete, mod) 328 if err := tf.Transform(g); err != nil { 329 t.Fatalf("err: %s", err) 330 } 331 } 332 333 expected := `module.moda.test_object.a 334 provider["registry.opentofu.org/hashicorp/test"] 335 provider["registry.opentofu.org/hashicorp/test"]` 336 337 actual := strings.TrimSpace(g.String()) 338 if actual != expected { 339 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 340 } 341 } 342 343 // Verify that configurations which are not recommended yet supported still work 344 func TestProviderConfigTransformer_nestedModuleProviders(t *testing.T) { 345 mod := testModuleInline(t, map[string]string{ 346 "main.tf": ` 347 terraform { 348 required_providers { 349 test = { 350 source = "registry.opentofu.org/hashicorp/test" 351 } 352 } 353 } 354 355 provider "test" { 356 alias = "z" 357 test_string = "config" 358 } 359 360 module "moda" { 361 source = "./moda" 362 providers = { 363 test.x = test.z 364 } 365 } 366 `, 367 368 "moda/main.tf": ` 369 terraform { 370 required_providers { 371 test = { 372 source = "registry.opentofu.org/hashicorp/test" 373 configuration_aliases = [ test.x ] 374 } 375 } 376 } 377 378 provider "test" { 379 test_string = "config" 380 } 381 382 // this should connect to this module's provider 383 resource "test_object" "a" { 384 } 385 386 resource "test_object" "x" { 387 provider = test.x 388 } 389 390 module "modb" { 391 source = "./modb" 392 } 393 `, 394 395 "moda/modb/main.tf": ` 396 # this should end up with the provider from the parent module 397 resource "test_object" "a" { 398 } 399 `, 400 }) 401 concrete := func(a *NodeAbstractProvider) dag.Vertex { return a } 402 403 g := testProviderTransformerGraph(t, mod) 404 { 405 tf := testTransformProviders(concrete, mod) 406 if err := tf.Transform(g); err != nil { 407 t.Fatalf("err: %s", err) 408 } 409 } 410 411 expected := `module.moda.module.modb.test_object.a 412 module.moda.provider["registry.opentofu.org/hashicorp/test"] 413 module.moda.provider["registry.opentofu.org/hashicorp/test"] 414 module.moda.test_object.a 415 module.moda.provider["registry.opentofu.org/hashicorp/test"] 416 module.moda.test_object.x 417 provider["registry.opentofu.org/hashicorp/test"].z 418 provider["registry.opentofu.org/hashicorp/test"].z` 419 420 actual := strings.TrimSpace(g.String()) 421 if actual != expected { 422 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 423 } 424 } 425 426 func TestProviderConfigTransformer_duplicateLocalName(t *testing.T) { 427 mod := testModuleInline(t, map[string]string{ 428 "main.tf": ` 429 terraform { 430 required_providers { 431 # We have to allow this since it wasn't previously prevented. If the 432 # default config is equivalent to the provider config, the user may never 433 # see an error. 434 dupe = { 435 source = "registry.opentofu.org/hashicorp/test" 436 } 437 } 438 } 439 440 provider "test" { 441 } 442 `}) 443 concrete := func(a *NodeAbstractProvider) dag.Vertex { return a } 444 445 g := testProviderTransformerGraph(t, mod) 446 tf := ProviderConfigTransformer{ 447 Config: mod, 448 Concrete: concrete, 449 } 450 if err := tf.Transform(g); err != nil { 451 t.Fatalf("err: %s", err) 452 } 453 454 expected := `provider["registry.opentofu.org/hashicorp/test"]` 455 456 actual := strings.TrimSpace(g.String()) 457 if actual != expected { 458 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 459 } 460 } 461 462 const testTransformProviderBasicStr = ` 463 aws_instance.web 464 provider["registry.opentofu.org/hashicorp/aws"] 465 provider["registry.opentofu.org/hashicorp/aws"] 466 ` 467 468 const testTransformCloseProviderBasicStr = ` 469 aws_instance.web 470 provider["registry.opentofu.org/hashicorp/aws"] 471 provider["registry.opentofu.org/hashicorp/aws"] 472 provider["registry.opentofu.org/hashicorp/aws"] (close) 473 aws_instance.web 474 provider["registry.opentofu.org/hashicorp/aws"] 475 ` 476 477 const testTransformMissingProviderBasicStr = ` 478 aws_instance.web 479 provider["registry.opentofu.org/hashicorp/aws"] 480 foo_instance.web 481 provider["registry.opentofu.org/hashicorp/foo"] 482 provider["registry.opentofu.org/hashicorp/aws"] 483 provider["registry.opentofu.org/hashicorp/aws"] (close) 484 aws_instance.web 485 provider["registry.opentofu.org/hashicorp/aws"] 486 provider["registry.opentofu.org/hashicorp/foo"] 487 provider["registry.opentofu.org/hashicorp/foo"] (close) 488 foo_instance.web 489 provider["registry.opentofu.org/hashicorp/foo"] 490 ` 491 492 const testTransformMissingGrandchildProviderStr = ` 493 module.sub.module.subsub.bar_instance.two 494 provider["registry.opentofu.org/hashicorp/bar"] 495 module.sub.module.subsub.foo_instance.one 496 module.sub.provider["registry.opentofu.org/hashicorp/foo"] 497 module.sub.provider["registry.opentofu.org/hashicorp/foo"] 498 provider["registry.opentofu.org/hashicorp/bar"] 499 ` 500 501 const testTransformPruneProviderBasicStr = ` 502 foo_instance.web 503 provider["registry.opentofu.org/hashicorp/foo"] 504 provider["registry.opentofu.org/hashicorp/foo"] 505 provider["registry.opentofu.org/hashicorp/foo"] (close) 506 foo_instance.web 507 provider["registry.opentofu.org/hashicorp/foo"] 508 ` 509 510 const testTransformModuleProviderConfigStr = ` 511 module.child.aws_instance.thing 512 provider["registry.opentofu.org/hashicorp/aws"].foo 513 provider["registry.opentofu.org/hashicorp/aws"].foo 514 ` 515 516 const testTransformModuleProviderGrandparentStr = ` 517 module.child.module.grandchild.aws_instance.baz 518 provider["registry.opentofu.org/hashicorp/aws"].foo 519 provider["registry.opentofu.org/hashicorp/aws"].foo 520 `