github.com/opentofu/opentofu@v1.7.1/internal/tofu/node_resource_abstract_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  	"testing"
    11  
    12  	"github.com/opentofu/opentofu/internal/addrs"
    13  	"github.com/opentofu/opentofu/internal/configs"
    14  	"github.com/opentofu/opentofu/internal/configs/configschema"
    15  	"github.com/opentofu/opentofu/internal/providers"
    16  	"github.com/opentofu/opentofu/internal/states"
    17  	"github.com/zclconf/go-cty/cty"
    18  )
    19  
    20  func TestNodeAbstractResourceProvider(t *testing.T) {
    21  	tests := []struct {
    22  		Addr   addrs.ConfigResource
    23  		Config *configs.Resource
    24  		Want   addrs.Provider
    25  	}{
    26  		{
    27  			Addr: addrs.Resource{
    28  				Mode: addrs.ManagedResourceMode,
    29  				Type: "null_resource",
    30  				Name: "baz",
    31  			}.InModule(addrs.RootModule),
    32  			Want: addrs.Provider{
    33  				Hostname:  addrs.DefaultProviderRegistryHost,
    34  				Namespace: "hashicorp",
    35  				Type:      "null",
    36  			},
    37  		},
    38  		{
    39  			Addr: addrs.Resource{
    40  				Mode: addrs.DataResourceMode,
    41  				Type: "terraform_remote_state",
    42  				Name: "baz",
    43  			}.InModule(addrs.RootModule),
    44  			Want: addrs.Provider{
    45  				// As a special case, the type prefix "terraform_" maps to
    46  				// the builtin provider, not the default one.
    47  				Hostname:  addrs.BuiltInProviderHost,
    48  				Namespace: addrs.BuiltInProviderNamespace,
    49  				Type:      "terraform",
    50  			},
    51  		},
    52  		{
    53  			Addr: addrs.Resource{
    54  				Mode: addrs.ManagedResourceMode,
    55  				Type: "null_resource",
    56  				Name: "baz",
    57  			}.InModule(addrs.RootModule),
    58  			Config: &configs.Resource{
    59  				// Just enough configs.Resource for the Provider method. Not
    60  				// actually valid for general use.
    61  				Provider: addrs.Provider{
    62  					Hostname:  addrs.DefaultProviderRegistryHost,
    63  					Namespace: "awesomecorp",
    64  					Type:      "happycloud",
    65  				},
    66  			},
    67  			// The config overrides the default behavior.
    68  			Want: addrs.Provider{
    69  				Hostname:  addrs.DefaultProviderRegistryHost,
    70  				Namespace: "awesomecorp",
    71  				Type:      "happycloud",
    72  			},
    73  		},
    74  		{
    75  			Addr: addrs.Resource{
    76  				Mode: addrs.DataResourceMode,
    77  				Type: "terraform_remote_state",
    78  				Name: "baz",
    79  			}.InModule(addrs.RootModule),
    80  			Config: &configs.Resource{
    81  				// Just enough configs.Resource for the Provider method. Not
    82  				// actually valid for general use.
    83  				Provider: addrs.Provider{
    84  					Hostname:  addrs.DefaultProviderRegistryHost,
    85  					Namespace: "awesomecorp",
    86  					Type:      "happycloud",
    87  				},
    88  			},
    89  			// The config overrides the default behavior.
    90  			Want: addrs.Provider{
    91  				Hostname:  addrs.DefaultProviderRegistryHost,
    92  				Namespace: "awesomecorp",
    93  				Type:      "happycloud",
    94  			},
    95  		},
    96  	}
    97  
    98  	for _, test := range tests {
    99  		var name string
   100  		if test.Config != nil {
   101  			name = fmt.Sprintf("%s with configured %s", test.Addr, test.Config.Provider)
   102  		} else {
   103  			name = fmt.Sprintf("%s with no configuration", test.Addr)
   104  		}
   105  		t.Run(name, func(t *testing.T) {
   106  			node := &NodeAbstractResource{
   107  				// Just enough NodeAbstractResource for the Provider function.
   108  				// (This would not be valid for some other functions.)
   109  				Addr:   test.Addr,
   110  				Config: test.Config,
   111  			}
   112  			got := node.Provider()
   113  			if got != test.Want {
   114  				t.Errorf("wrong result\naddr:  %s\nconfig: %#v\ngot:   %s\nwant:  %s", test.Addr, test.Config, got, test.Want)
   115  			}
   116  		})
   117  	}
   118  }
   119  
   120  // Make sure ProvideBy returns the final resolved provider
   121  func TestNodeAbstractResourceSetProvider(t *testing.T) {
   122  	node := &NodeAbstractResource{
   123  
   124  		// Just enough NodeAbstractResource for the Provider function.
   125  		// (This would not be valid for some other functions.)
   126  		Addr: addrs.Resource{
   127  			Mode: addrs.DataResourceMode,
   128  			Type: "terraform_remote_state",
   129  			Name: "baz",
   130  		}.InModule(addrs.RootModule),
   131  		Config: &configs.Resource{
   132  			Mode: addrs.ManagedResourceMode,
   133  			Type: "terraform_remote_state",
   134  			Name: "baz",
   135  			// Just enough configs.Resource for the Provider method. Not
   136  			// actually valid for general use.
   137  			Provider: addrs.Provider{
   138  				Hostname:  addrs.DefaultProviderRegistryHost,
   139  				Namespace: "awesomecorp",
   140  				Type:      "happycloud",
   141  			},
   142  		},
   143  	}
   144  
   145  	p, exact := node.ProvidedBy()
   146  	if exact {
   147  		t.Fatalf("no exact provider should be found from this confniguration, got %q\n", p)
   148  	}
   149  
   150  	// the implied non-exact provider should be "terraform"
   151  	lpc, ok := p.(addrs.LocalProviderConfig)
   152  	if !ok {
   153  		t.Fatalf("expected LocalProviderConfig, got %#v\n", p)
   154  	}
   155  
   156  	if lpc.LocalName != "terraform" {
   157  		t.Fatalf("expected non-exact provider of 'terraform', got %q", lpc.LocalName)
   158  	}
   159  
   160  	// now set a resolved provider for the resource
   161  	resolved := addrs.AbsProviderConfig{
   162  		Provider: addrs.Provider{
   163  			Hostname:  addrs.DefaultProviderRegistryHost,
   164  			Namespace: "awesomecorp",
   165  			Type:      "happycloud",
   166  		},
   167  		Module: addrs.RootModule,
   168  		Alias:  "test",
   169  	}
   170  
   171  	node.SetProvider(resolved)
   172  	p, exact = node.ProvidedBy()
   173  	if !exact {
   174  		t.Fatalf("exact provider should be found, got %q\n", p)
   175  	}
   176  
   177  	apc, ok := p.(addrs.AbsProviderConfig)
   178  	if !ok {
   179  		t.Fatalf("expected AbsProviderConfig, got %#v\n", p)
   180  	}
   181  
   182  	if apc.String() != resolved.String() {
   183  		t.Fatalf("incorrect resolved config: got %#v, wanted %#v\n", apc, resolved)
   184  	}
   185  }
   186  
   187  func TestNodeAbstractResource_ReadResourceInstanceState(t *testing.T) {
   188  	mockProvider := mockProviderWithResourceTypeSchema("aws_instance", &configschema.Block{
   189  		Attributes: map[string]*configschema.Attribute{
   190  			"id": {
   191  				Type:     cty.String,
   192  				Optional: true,
   193  			},
   194  		},
   195  	})
   196  	// This test does not configure the provider, but the mock provider will
   197  	// check that this was called and report errors.
   198  	mockProvider.ConfigureProviderCalled = true
   199  
   200  	tests := map[string]struct {
   201  		State              *states.State
   202  		Node               *NodeAbstractResource
   203  		ExpectedInstanceId string
   204  	}{
   205  		"ReadState gets primary instance state": {
   206  			State: states.BuildState(func(s *states.SyncState) {
   207  				providerAddr := addrs.AbsProviderConfig{
   208  					Provider: addrs.NewDefaultProvider("aws"),
   209  					Module:   addrs.RootModule,
   210  				}
   211  				oneAddr := addrs.Resource{
   212  					Mode: addrs.ManagedResourceMode,
   213  					Type: "aws_instance",
   214  					Name: "bar",
   215  				}.Absolute(addrs.RootModuleInstance)
   216  				s.SetResourceProvider(oneAddr, providerAddr)
   217  				s.SetResourceInstanceCurrent(oneAddr.Instance(addrs.NoKey), &states.ResourceInstanceObjectSrc{
   218  					Status:    states.ObjectReady,
   219  					AttrsJSON: []byte(`{"id":"i-abc123"}`),
   220  				}, providerAddr)
   221  			}),
   222  			Node: &NodeAbstractResource{
   223  				Addr:             mustConfigResourceAddr("aws_instance.bar"),
   224  				ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
   225  			},
   226  			ExpectedInstanceId: "i-abc123",
   227  		},
   228  	}
   229  
   230  	for k, test := range tests {
   231  		t.Run(k, func(t *testing.T) {
   232  			ctx := new(MockEvalContext)
   233  			ctx.StateState = test.State.SyncWrapper()
   234  			ctx.PathPath = addrs.RootModuleInstance
   235  			ctx.ProviderSchemaSchema = mockProvider.GetProviderSchema()
   236  
   237  			ctx.ProviderProvider = providers.Interface(mockProvider)
   238  
   239  			got, readDiags := test.Node.readResourceInstanceState(ctx, test.Node.Addr.Resource.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance))
   240  			if readDiags.HasErrors() {
   241  				t.Fatalf("[%s] Got err: %#v", k, readDiags.Err())
   242  			}
   243  
   244  			expected := test.ExpectedInstanceId
   245  
   246  			if !(got != nil && got.Value.GetAttr("id") == cty.StringVal(expected)) {
   247  				t.Fatalf("[%s] Expected output with ID %#v, got: %#v", k, expected, got)
   248  			}
   249  		})
   250  	}
   251  }
   252  
   253  func TestNodeAbstractResource_ReadResourceInstanceStateDeposed(t *testing.T) {
   254  	mockProvider := mockProviderWithResourceTypeSchema("aws_instance", &configschema.Block{
   255  		Attributes: map[string]*configschema.Attribute{
   256  			"id": {
   257  				Type:     cty.String,
   258  				Optional: true,
   259  			},
   260  		},
   261  	})
   262  	// This test does not configure the provider, but the mock provider will
   263  	// check that this was called and report errors.
   264  	mockProvider.ConfigureProviderCalled = true
   265  
   266  	tests := map[string]struct {
   267  		State              *states.State
   268  		Node               *NodeAbstractResource
   269  		ExpectedInstanceId string
   270  	}{
   271  		"ReadStateDeposed gets deposed instance": {
   272  			State: states.BuildState(func(s *states.SyncState) {
   273  				providerAddr := addrs.AbsProviderConfig{
   274  					Provider: addrs.NewDefaultProvider("aws"),
   275  					Module:   addrs.RootModule,
   276  				}
   277  				oneAddr := addrs.Resource{
   278  					Mode: addrs.ManagedResourceMode,
   279  					Type: "aws_instance",
   280  					Name: "bar",
   281  				}.Absolute(addrs.RootModuleInstance)
   282  				s.SetResourceProvider(oneAddr, providerAddr)
   283  				s.SetResourceInstanceDeposed(oneAddr.Instance(addrs.NoKey), states.DeposedKey("00000001"), &states.ResourceInstanceObjectSrc{
   284  					Status:    states.ObjectReady,
   285  					AttrsJSON: []byte(`{"id":"i-abc123"}`),
   286  				}, providerAddr)
   287  			}),
   288  			Node: &NodeAbstractResource{
   289  				Addr:             mustConfigResourceAddr("aws_instance.bar"),
   290  				ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
   291  			},
   292  			ExpectedInstanceId: "i-abc123",
   293  		},
   294  	}
   295  	for k, test := range tests {
   296  		t.Run(k, func(t *testing.T) {
   297  			ctx := new(MockEvalContext)
   298  			ctx.StateState = test.State.SyncWrapper()
   299  			ctx.PathPath = addrs.RootModuleInstance
   300  			ctx.ProviderSchemaSchema = mockProvider.GetProviderSchema()
   301  			ctx.ProviderProvider = providers.Interface(mockProvider)
   302  
   303  			key := states.DeposedKey("00000001") // shim from legacy state assigns 0th deposed index this key
   304  
   305  			got, readDiags := test.Node.readResourceInstanceStateDeposed(ctx, test.Node.Addr.Resource.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), key)
   306  			if readDiags.HasErrors() {
   307  				t.Fatalf("[%s] Got err: %#v", k, readDiags.Err())
   308  			}
   309  
   310  			expected := test.ExpectedInstanceId
   311  
   312  			if !(got != nil && got.Value.GetAttr("id") == cty.StringVal(expected)) {
   313  				t.Fatalf("[%s] Expected output with ID %#v, got: %#v", k, expected, got)
   314  			}
   315  		})
   316  	}
   317  }