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  `