diff --git a/.golangci.yml b/.golangci.yml index 5b0c982e00..6325e7889e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -30,7 +30,7 @@ linters-settings: require-specific: true cyclop: - max-complexity: 15 + max-complexity: 20 gocognit: min-complexity: 50 @@ -38,6 +38,14 @@ linters-settings: goconst: ignore-tests: true # Is documented to be the default behaviour, but that doesn't seem to be the case + gocritic: + settings: + ifElseChain: + minThreshold: 4 + + nestif: + min-complexity: 6 + issues: exclude-rules: - path: (.+)_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 56187b076c..9ea7f33e24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ ENHANCEMENTS: * Added a help target to the Makefile. ([#1925](https://github.com/opentofu/opentofu/pull/1925)) * Added a simplified Build Process with a Makefile Target ([#1926](https://github.com/opentofu/opentofu/issues/1926)) * Ensures that the Makefile adheres to POSIX standards ([#1811](https://github.com/opentofu/opentofu/pull/1928)) +* Added for-each support to providers ([#300](https://github.com/opentofu/opentofu/issues/300)) * Added consolidate warnings and errors flags ([#1894](https://github.com/opentofu/opentofu/pull/1894)) BUG FIXES: diff --git a/internal/addrs/provider_config.go b/internal/addrs/provider_config.go index 4e76100246..536793a44a 100644 --- a/internal/addrs/provider_config.go +++ b/internal/addrs/provider_config.go @@ -113,8 +113,26 @@ var _ ProviderConfig = AbsProviderConfig{} // This type of address is typically not used prominently in the UI, except in // error messages that refer to provider configurations. func ParseAbsProviderConfig(traversal hcl.Traversal) (AbsProviderConfig, tfdiags.Diagnostics) { + pc, key, diags := ParseAbsProviderConfigInstance(traversal) + if key != NoKey { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid provider configuration address", + Detail: "A provider address must not include an instance key.", + Subject: traversal.SourceRange().Ptr(), + }) + } + return pc, diags +} + +// ParseAbsProviderConfigInstance behaves identically to ParseAbsProviderConfig, but additionally +// allows an instance key after the alias. +// +//nolint:mnd // traversals with specific indices +func ParseAbsProviderConfigInstance(traversal hcl.Traversal) (AbsProviderConfig, InstanceKey, tfdiags.Diagnostics) { modInst, remain, diags := parseModuleInstancePrefix(traversal) var ret AbsProviderConfig + var key InstanceKey // Providers cannot resolve within module instances, so verify that there // are no instance keys in the module path before converting to a Module. @@ -123,10 +141,10 @@ func ParseAbsProviderConfig(traversal hcl.Traversal) (AbsProviderConfig, tfdiags diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid provider configuration address", - Detail: "Provider address cannot contain module indexes", + Detail: "A provider configuration must not appear in a module instance that uses count or for_each.", Subject: remain.SourceRange().Ptr(), }) - return ret, diags + return ret, key, diags } } ret.Module = modInst.Module() @@ -138,16 +156,16 @@ func ParseAbsProviderConfig(traversal hcl.Traversal) (AbsProviderConfig, tfdiags Detail: "Provider address must begin with \"provider.\", followed by a provider type name.", Subject: remain.SourceRange().Ptr(), }) - return ret, diags + return ret, key, diags } - if len(remain) > 3 { + if len(remain) > 4 { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid provider configuration address", - Detail: "Extraneous operators after provider configuration alias.", - Subject: hcl.Traversal(remain[3:]).SourceRange().Ptr(), + Detail: "Extraneous operators after provider configuration reference.", + Subject: remain[4:].SourceRange().Ptr(), }) - return ret, diags + return ret, key, diags } if tt, ok := remain[1].(hcl.TraverseIndex); ok { @@ -158,13 +176,13 @@ func ParseAbsProviderConfig(traversal hcl.Traversal) (AbsProviderConfig, tfdiags Detail: "The prefix \"provider.\" must be followed by a provider type name.", Subject: remain[1].SourceRange().Ptr(), }) - return ret, diags + return ret, key, diags } p, sourceDiags := ParseProviderSourceString(tt.Key.AsString()) ret.Provider = p if sourceDiags.HasErrors() { diags = diags.Append(sourceDiags) - return ret, diags + return ret, key, diags } } else { diags = diags.Append(&hcl.Diagnostic{ @@ -173,10 +191,10 @@ func ParseAbsProviderConfig(traversal hcl.Traversal) (AbsProviderConfig, tfdiags Detail: "The prefix \"provider.\" must be followed by a provider type name.", Subject: remain[1].SourceRange().Ptr(), }) - return ret, diags + return ret, key, diags } - if len(remain) == 3 { + if len(remain) > 2 { if tt, ok := remain[2].(hcl.TraverseAttr); ok { ret.Alias = tt.Name } else { @@ -186,11 +204,34 @@ func ParseAbsProviderConfig(traversal hcl.Traversal) (AbsProviderConfig, tfdiags Detail: "Provider type name must be followed by a configuration alias name.", Subject: remain[2].SourceRange().Ptr(), }) - return ret, diags + return ret, key, diags } } - return ret, diags + if len(remain) > 3 { + if tt, ok := remain[3].(hcl.TraverseIndex); ok { + var keyErr error + key, keyErr = ParseInstanceKey(tt.Key) + if keyErr != nil { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid provider configuration address", + Detail: fmt.Sprintf("Invalid provider instance key: %s.", keyErr.Error()), + Subject: remain[3].SourceRange().Ptr(), + }) + } + } else { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid provider configuration address", + Detail: "A provider configuration alias can be followed only by an instance key in brackets.", + Subject: remain[3].SourceRange().Ptr(), + }) + return ret, key, diags + } + } + + return ret, key, diags } // ParseAbsProviderConfigStr is a helper wrapper around ParseAbsProviderConfig @@ -219,6 +260,17 @@ func ParseAbsProviderConfigStr(str string) (AbsProviderConfig, tfdiags.Diagnosti diags = diags.Append(addrDiags) return addr, diags } +func ParseAbsProviderConfigInstanceStr(str string) (AbsProviderConfig, InstanceKey, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1}) + diags = diags.Append(parseDiags) + if parseDiags.HasErrors() { + return AbsProviderConfig{}, nil, diags + } + addr, key, addrDiags := ParseAbsProviderConfigInstance(traversal) + diags = diags.Append(addrDiags) + return addr, key, diags +} func ParseLegacyAbsProviderConfigStr(str string) (AbsProviderConfig, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics @@ -413,3 +465,10 @@ func (pc AbsProviderConfig) String() string { return strings.Join(parts, ".") } + +func (pc AbsProviderConfig) InstanceString(key InstanceKey) string { + if key == NoKey { + return pc.String() + } + return pc.String() + key.String() +} diff --git a/internal/addrs/provider_config_test.go b/internal/addrs/provider_config_test.go index da93a1645b..75331b1198 100644 --- a/internal/addrs/provider_config_test.go +++ b/internal/addrs/provider_config_test.go @@ -15,10 +15,11 @@ import ( "github.com/hashicorp/hcl/v2/hclsyntax" ) -func TestParseAbsProviderConfig(t *testing.T) { +func TestParseAbsProviderConfigInstances(t *testing.T) { tests := []struct { Input string Want AbsProviderConfig + WantKey InstanceKey WantDiag string }{ { @@ -31,6 +32,7 @@ func TestParseAbsProviderConfig(t *testing.T) { Hostname: "registry.opentofu.org", }, }, + NoKey, ``, }, { @@ -44,6 +46,7 @@ func TestParseAbsProviderConfig(t *testing.T) { }, Alias: "foo", }, + NoKey, ``, }, { @@ -56,6 +59,7 @@ func TestParseAbsProviderConfig(t *testing.T) { Hostname: "registry.opentofu.org", }, }, + NoKey, ``, }, { @@ -69,56 +73,81 @@ func TestParseAbsProviderConfig(t *testing.T) { }, Alias: "foo", }, + NoKey, + ``, + }, + { + `module.baz.provider["registry.opentofu.org/hashicorp/aws"].foo["keystr"]`, + AbsProviderConfig{ + Module: Module{"baz"}, + Provider: Provider{ + Type: "aws", + Namespace: "hashicorp", + Hostname: "registry.opentofu.org", + }, + Alias: "foo", + }, + StringKey("keystr"), ``, }, { `module.baz["foo"].provider["registry.opentofu.org/hashicorp/aws"]`, AbsProviderConfig{}, - `Provider address cannot contain module indexes`, + NoKey, + `A provider configuration must not appear in a module instance that uses count or for_each.`, }, { `module.baz[1].provider["registry.opentofu.org/hashicorp/aws"]`, AbsProviderConfig{}, - `Provider address cannot contain module indexes`, + NoKey, + `A provider configuration must not appear in a module instance that uses count or for_each.`, }, { `module.baz[1].module.bar.provider["registry.opentofu.org/hashicorp/aws"]`, AbsProviderConfig{}, - `Provider address cannot contain module indexes`, + NoKey, + `A provider configuration must not appear in a module instance that uses count or for_each.`, }, { `aws`, AbsProviderConfig{}, + NoKey, `Provider address must begin with "provider.", followed by a provider type name.`, }, { `aws.foo`, AbsProviderConfig{}, + NoKey, `Provider address must begin with "provider.", followed by a provider type name.`, }, { `provider`, AbsProviderConfig{}, + NoKey, `Provider address must begin with "provider.", followed by a provider type name.`, }, { - `provider.aws.foo.bar`, + `provider.aws.foo["bar"].baz`, AbsProviderConfig{}, - `Extraneous operators after provider configuration alias.`, + NoKey, + `Extraneous operators after provider configuration reference.`, }, { `provider["aws"]["foo"]`, AbsProviderConfig{}, + NoKey, `Provider type name must be followed by a configuration alias name.`, }, { `module.foo`, AbsProviderConfig{}, + NoKey, `Provider address must begin with "provider.", followed by a provider type name.`, }, { `provider[0]`, AbsProviderConfig{}, + NoKey, `The prefix "provider." must be followed by a provider type name.`, }, } @@ -134,7 +163,7 @@ func TestParseAbsProviderConfig(t *testing.T) { return } - got, diags := ParseAbsProviderConfig(traversal) + got, key, diags := ParseAbsProviderConfigInstance(traversal) if test.WantDiag != "" { if len(diags) != 1 { @@ -154,6 +183,10 @@ func TestParseAbsProviderConfig(t *testing.T) { for _, problem := range deep.Equal(got, test.Want) { t.Error(problem) } + + if test.WantKey != key { + t.Errorf("Wanted key %s, got key %s", test.WantKey, key) + } }) } } diff --git a/internal/backend/local/backend_plan_test.go b/internal/backend/local/backend_plan_test.go index 67158d4841..e42b73884c 100644 --- a/internal/backend/local/backend_plan_test.go +++ b/internal/backend/local/backend_plan_test.go @@ -369,6 +369,7 @@ func TestLocal_planDeposedOnly(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) })) outDir := t.TempDir() @@ -765,6 +766,7 @@ func testPlanState() *states.State { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) return state } @@ -792,6 +794,7 @@ func testPlanState_withDataSource() *states.State { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) rootModule.SetResourceInstanceCurrent( addrs.Resource{ @@ -809,6 +812,7 @@ func testPlanState_withDataSource() *states.State { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) return state } @@ -836,6 +840,7 @@ func testPlanState_tainted() *states.State { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) return state } diff --git a/internal/backend/local/backend_refresh_test.go b/internal/backend/local/backend_refresh_test.go index c4064aae2c..fe7976a9d1 100644 --- a/internal/backend/local/backend_refresh_test.go +++ b/internal/backend/local/backend_refresh_test.go @@ -300,6 +300,7 @@ func testRefreshState() *states.State { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) return state } diff --git a/internal/backend/testing.go b/internal/backend/testing.go index cf7059351b..fa7941e9bf 100644 --- a/internal/backend/testing.go +++ b/internal/backend/testing.go @@ -159,6 +159,7 @@ func TestBackendStates(t *testing.T, b Backend) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) // write a distinct known state to bar diff --git a/internal/command/apply_destroy_test.go b/internal/command/apply_destroy_test.go index a7f1a06e03..8d6ee801f6 100644 --- a/internal/command/apply_destroy_test.go +++ b/internal/command/apply_destroy_test.go @@ -44,6 +44,7 @@ func TestApply_destroy(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, originalState) @@ -149,6 +150,7 @@ func TestApply_destroyApproveNo(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, originalState) @@ -217,6 +219,7 @@ func TestApply_destroyApproveYes(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, originalState) @@ -287,6 +290,7 @@ func TestApply_destroyLockedState(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, originalState) @@ -415,6 +419,7 @@ func TestApply_targetedDestroy(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }, }, @@ -456,6 +461,7 @@ func TestApply_targetedDestroy(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }, }, @@ -483,6 +489,7 @@ func TestApply_targetedDestroy(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -499,6 +506,7 @@ func TestApply_targetedDestroy(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) diff --git a/internal/command/apply_test.go b/internal/command/apply_test.go index 0f573ebf82..06f48d9652 100644 --- a/internal/command/apply_test.go +++ b/internal/command/apply_test.go @@ -1000,6 +1000,7 @@ func TestApply_refresh(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, originalState) @@ -1067,6 +1068,7 @@ func TestApply_refreshFalse(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, originalState) @@ -1204,6 +1206,7 @@ func TestApply_state(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, originalState) @@ -1601,6 +1604,7 @@ func TestApply_backup(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, originalState) @@ -2128,6 +2132,7 @@ func TestApply_replace(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, originalState) diff --git a/internal/command/command_test.go b/internal/command/command_test.go index fc4b39074a..57580f0805 100644 --- a/internal/command/command_test.go +++ b/internal/command/command_test.go @@ -311,6 +311,7 @@ func testState() *states.State { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) // DeepCopy is used here to ensure our synthetic state matches exactly // with a state that will have been copied during the command diff --git a/internal/command/jsonformat/state_test.go b/internal/command/jsonformat/state_test.go index 1708be00a3..2b6c9f6401 100644 --- a/internal/command/jsonformat/state_test.go +++ b/internal/command/jsonformat/state_test.go @@ -271,6 +271,7 @@ func basicState(t *testing.T) *states.State { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) rootModule.SetResourceInstanceCurrent( addrs.Resource{ @@ -287,6 +288,7 @@ func basicState(t *testing.T) *states.State { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) return state } @@ -323,6 +325,7 @@ func stateWithMoreOutputs(t *testing.T) *states.State { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) return state } @@ -350,6 +353,7 @@ func nestedState(t *testing.T) *states.State { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) return state } @@ -373,6 +377,7 @@ func deposedState(t *testing.T) *states.State { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) return state } @@ -402,6 +407,7 @@ func onlyDeposedState(t *testing.T) *states.State { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) rootModule.SetResourceInstanceDeposed( addrs.Resource{ @@ -419,6 +425,7 @@ func onlyDeposedState(t *testing.T) *states.State { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) return state } diff --git a/internal/command/jsonstate/state_test.go b/internal/command/jsonstate/state_test.go index 4f65d1f91c..d0d4c3a3b6 100644 --- a/internal/command/jsonstate/state_test.go +++ b/internal/command/jsonstate/state_test.go @@ -631,6 +631,7 @@ func TestMarshalModules_basic(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -646,6 +647,7 @@ func TestMarshalModules_basic(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: childModule.Module(), }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -661,6 +663,7 @@ func TestMarshalModules_basic(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: subModule.Module(), }, + addrs.NoKey, ) }) moduleMap := make(map[string][]addrs.ModuleInstance) @@ -700,6 +703,7 @@ func TestMarshalModules_nested(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -715,6 +719,7 @@ func TestMarshalModules_nested(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: childModule.Module(), }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -730,6 +735,7 @@ func TestMarshalModules_nested(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: subModule.Module(), }, + addrs.NoKey, ) }) moduleMap := make(map[string][]addrs.ModuleInstance) @@ -772,6 +778,7 @@ func TestMarshalModules_parent_no_resources(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -787,6 +794,7 @@ func TestMarshalModules_parent_no_resources(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: subModule.Module(), }, + addrs.NoKey, ) }) got, err := marshalRootModule(testState, testSchemas()) diff --git a/internal/command/plan_test.go b/internal/command/plan_test.go index 2bccf78a23..87176f4caf 100644 --- a/internal/command/plan_test.go +++ b/internal/command/plan_test.go @@ -153,6 +153,7 @@ func TestPlan_destroy(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) outPath := testTempFile(t) @@ -316,6 +317,7 @@ func TestPlan_outPathNoChange(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, originalState) @@ -417,6 +419,7 @@ func TestPlan_outBackend(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) @@ -1507,6 +1510,7 @@ func TestPlan_replace(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, originalState) diff --git a/internal/command/show_test.go b/internal/command/show_test.go index 8c0206e851..db424fcdb3 100644 --- a/internal/command/show_test.go +++ b/internal/command/show_test.go @@ -148,6 +148,7 @@ func TestShow_argsWithStateAliasedProvider(t *testing.T) { Dependencies: []addrs.ConfigResource{}, }, addrs.RootModuleInstance.ProviderConfigAliased(addrs.NewDefaultProvider("test"), "alias"), + addrs.NoKey, ) }) diff --git a/internal/command/state_mv_test.go b/internal/command/state_mv_test.go index 77218853d5..e74bab5c2d 100644 --- a/internal/command/state_mv_test.go +++ b/internal/command/state_mv_test.go @@ -35,6 +35,7 @@ func TestStateMv(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -51,6 +52,7 @@ func TestStateMv(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -171,6 +173,7 @@ func TestStateMv_backupAndBackupOutOptionsWithNonLocalBackend(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) @@ -418,6 +421,7 @@ func TestStateMv_resourceToInstance(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -434,6 +438,7 @@ func TestStateMv_resourceToInstance(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceProvider( addrs.Resource{ @@ -509,6 +514,7 @@ func TestStateMv_resourceToInstanceErr(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceProvider( addrs.Resource{ @@ -578,6 +584,7 @@ func TestStateMv_resourceToInstanceErrInAutomation(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceProvider( addrs.Resource{ @@ -648,6 +655,7 @@ func TestStateMv_instanceToResource(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -663,6 +671,7 @@ func TestStateMv_instanceToResource(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -738,6 +747,7 @@ func TestStateMv_instanceToNewResource(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -811,6 +821,7 @@ func TestStateMv_differentResourceTypes(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -873,6 +884,7 @@ func TestStateMv_explicitWithBackend(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -888,6 +900,7 @@ func TestStateMv_explicitWithBackend(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -951,6 +964,7 @@ func TestStateMv_backupExplicit(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -967,6 +981,7 @@ func TestStateMv_backupExplicit(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -1018,6 +1033,7 @@ func TestStateMv_stateOutNew(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -1074,6 +1090,7 @@ func TestStateMv_stateOutExisting(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, stateSrc) @@ -1093,6 +1110,7 @@ func TestStateMv_stateOutExisting(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) stateOutPath := testStateFile(t, stateDst) @@ -1176,6 +1194,7 @@ func TestStateMv_stateOutNew_count(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -1191,6 +1210,7 @@ func TestStateMv_stateOutNew_count(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -1206,6 +1226,7 @@ func TestStateMv_stateOutNew_count(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -1266,6 +1287,7 @@ func TestStateMv_stateOutNew_largeCount(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) } s.SetResourceInstanceCurrent( @@ -1282,6 +1304,7 @@ func TestStateMv_stateOutNew_largeCount(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -1338,6 +1361,7 @@ func TestStateMv_stateOutNew_nestedModule(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -1353,6 +1377,7 @@ func TestStateMv_stateOutNew_nestedModule(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) @@ -1410,6 +1435,7 @@ func TestStateMv_toNewModule(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) @@ -1484,6 +1510,7 @@ func TestStateMv_withinBackend(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -1500,6 +1527,7 @@ func TestStateMv_withinBackend(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) @@ -1559,6 +1587,7 @@ func TestStateMv_fromBackendToLocal(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) state.Module(addrs.RootModuleInstance).SetResourceInstanceCurrent( mustResourceAddr("test_instance.baz").Resource.Instance(addrs.NoKey), @@ -1570,6 +1599,7 @@ func TestStateMv_fromBackendToLocal(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) // the local backend state file is "foo" @@ -1635,6 +1665,7 @@ func TestStateMv_onlyResourceInModule(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) @@ -1730,6 +1761,7 @@ func TestStateMv_checkRequiredVersion(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -1746,6 +1778,7 @@ func TestStateMv_checkRequiredVersion(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) diff --git a/internal/command/state_replace_provider_test.go b/internal/command/state_replace_provider_test.go index 4446719833..15745906a5 100644 --- a/internal/command/state_replace_provider_test.go +++ b/internal/command/state_replace_provider_test.go @@ -33,6 +33,7 @@ func TestStateReplaceProvider(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -48,6 +49,7 @@ func TestStateReplaceProvider(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -63,6 +65,7 @@ func TestStateReplaceProvider(t *testing.T) { Provider: addrs.NewLegacyProvider("azurerm"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) @@ -320,6 +323,7 @@ func TestStateReplaceProvider_checkRequiredVersion(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -335,6 +339,7 @@ func TestStateReplaceProvider_checkRequiredVersion(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -350,6 +355,7 @@ func TestStateReplaceProvider_checkRequiredVersion(t *testing.T) { Provider: addrs.NewLegacyProvider("azurerm"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) diff --git a/internal/command/state_rm_test.go b/internal/command/state_rm_test.go index 98f345fd8e..8dd012e5fa 100644 --- a/internal/command/state_rm_test.go +++ b/internal/command/state_rm_test.go @@ -33,6 +33,7 @@ func TestStateRm(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -48,6 +49,7 @@ func TestStateRm(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -100,6 +102,7 @@ func TestStateRmNotChildModule(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) // This second instance has the same local address as the first but // is in a child module. Older versions of Terraform would incorrectly @@ -118,6 +121,7 @@ func TestStateRmNotChildModule(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -191,6 +195,7 @@ func TestStateRmNoArgs(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -206,6 +211,7 @@ func TestStateRmNoArgs(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -252,6 +258,7 @@ func TestStateRmNonExist(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -267,6 +274,7 @@ func TestStateRmNonExist(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -309,6 +317,7 @@ func TestStateRm_backupExplicit(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -324,6 +333,7 @@ func TestStateRm_backupExplicit(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -428,6 +438,7 @@ func TestStateRm_backendState(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -443,6 +454,7 @@ func TestStateRm_backendState(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) @@ -509,6 +521,7 @@ func TestStateRm_checkRequiredVersion(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -524,6 +537,7 @@ func TestStateRm_checkRequiredVersion(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) diff --git a/internal/command/state_show.go b/internal/command/state_show.go index 42b43f80f7..11ce419df2 100644 --- a/internal/command/state_show.go +++ b/internal/command/state_show.go @@ -172,6 +172,7 @@ func (c *StateShowCommand) Run(args []string) int { addr.Resource, is.Current, absPc, + addrs.NoKey, ) root, outputs, err := jsonstate.MarshalForRenderer(statefile.New(singleInstance, "", 0), schemas) diff --git a/internal/command/state_show_test.go b/internal/command/state_show_test.go index 39b00b5538..0a889c5828 100644 --- a/internal/command/state_show_test.go +++ b/internal/command/state_show_test.go @@ -34,6 +34,7 @@ func TestStateShow(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -96,6 +97,7 @@ func TestStateShow_multi(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -111,6 +113,7 @@ func TestStateShow_multi(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: submod.Module(), }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -222,6 +225,7 @@ func TestStateShow_configured_provider(t *testing.T) { Provider: addrs.NewDefaultProvider("test-beta"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -364,6 +368,7 @@ func stateWithSensitiveValueForStateShow() *states.State { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) diff --git a/internal/command/taint.go b/internal/command/taint.go index 52f8c2b199..5bd2dab804 100644 --- a/internal/command/taint.go +++ b/internal/command/taint.go @@ -186,7 +186,7 @@ func (c *TaintCommand) Run(args []string) int { } obj.Status = states.ObjectTainted - ss.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig) + ss.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig, is.ProviderKey) if err := stateMgr.WriteState(state); err != nil { c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) diff --git a/internal/command/taint_test.go b/internal/command/taint_test.go index 30bbd0b270..ba315031b8 100644 --- a/internal/command/taint_test.go +++ b/internal/command/taint_test.go @@ -33,6 +33,7 @@ func TestTaint(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -73,6 +74,7 @@ func TestTaint_lockedState(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -125,6 +127,7 @@ func TestTaint_backup(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) testStateFileDefault(t, state) @@ -169,6 +172,7 @@ func TestTaint_backupDisable(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) testStateFileDefault(t, state) @@ -236,6 +240,7 @@ func TestTaint_defaultState(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) testStateFileDefault(t, state) @@ -278,6 +283,7 @@ func TestTaint_defaultWorkspaceState(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) testWorkspace := "development" @@ -317,6 +323,7 @@ func TestTaint_missing(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -355,6 +362,7 @@ func TestTaint_missingAllow(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -411,6 +419,7 @@ func TestTaint_stateOut(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) testStateFileDefault(t, state) @@ -452,6 +461,7 @@ func TestTaint_module(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -467,6 +477,7 @@ func TestTaint_module(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -513,6 +524,7 @@ func TestTaint_checkRequiredVersion(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) path := testStateFile(t, state) diff --git a/internal/command/untaint.go b/internal/command/untaint.go index 3eefa2b427..5c1dac3346 100644 --- a/internal/command/untaint.go +++ b/internal/command/untaint.go @@ -187,7 +187,7 @@ func (c *UntaintCommand) Run(args []string) int { } obj.Status = states.ObjectReady - ss.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig) + ss.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig, is.ProviderKey) if err := stateMgr.WriteState(state); err != nil { c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) diff --git a/internal/command/untaint_test.go b/internal/command/untaint_test.go index bfe8af9e57..7359c68835 100644 --- a/internal/command/untaint_test.go +++ b/internal/command/untaint_test.go @@ -32,6 +32,7 @@ func TestUntaint(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -77,6 +78,7 @@ func TestUntaint_lockedState(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -129,6 +131,7 @@ func TestUntaint_backup(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) testStateFileDefault(t, state) @@ -184,6 +187,7 @@ func TestUntaint_backupDisable(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) testStateFileDefault(t, state) @@ -255,6 +259,7 @@ func TestUntaint_defaultState(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) testStateFileDefault(t, state) @@ -302,6 +307,7 @@ func TestUntaint_defaultWorkspaceState(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) testWorkspace := "development" @@ -345,6 +351,7 @@ func TestUntaint_missing(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -383,6 +390,7 @@ func TestUntaint_missingAllow(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) @@ -439,6 +447,7 @@ func TestUntaint_stateOut(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) testStateFileDefault(t, state) @@ -488,6 +497,7 @@ func TestUntaint_module(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -503,6 +513,7 @@ func TestUntaint_module(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) statePath := testStateFile(t, state) diff --git a/internal/command/views/operation_test.go b/internal/command/views/operation_test.go index 07e61c4230..c0e24ee416 100644 --- a/internal/command/views/operation_test.go +++ b/internal/command/views/operation_test.go @@ -309,6 +309,7 @@ func TestOperation_planNoChanges(t *testing.T) { AttrsJSON: []byte(`{}`), }, addrs.RootModuleInstance.ProviderConfigDefault(addrs.NewDefaultProvider("test")), + addrs.NoKey, ) }), PriorState: states.NewState(), diff --git a/internal/command/views/show_test.go b/internal/command/views/show_test.go index c7f4a98f30..45219c28e6 100644 --- a/internal/command/views/show_test.go +++ b/internal/command/views/show_test.go @@ -233,6 +233,7 @@ func testState() *states.State { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) // DeepCopy is used here to ensure our synthetic state matches exactly // with a state that will have been copied during the command diff --git a/internal/command/views/test_test.go b/internal/command/views/test_test.go index 063d34d447..72635f684f 100644 --- a/internal/command/views/test_test.go +++ b/internal/command/views/test_test.go @@ -665,7 +665,7 @@ Plan: 1 to add, 0 to change, 0 to destroy. Namespace: "hashicorp", Type: "test", }, - }) + }, addrs.NoKey) }), Config: &configs.Config{}, Providers: map[addrs.Provider]providers.ProviderSchema{ @@ -812,7 +812,7 @@ this time it is very bad addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -825,7 +825,7 @@ this time it is very bad addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) state.SetResourceInstanceDeposed( addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -839,7 +839,7 @@ this time it is very bad addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) }), stdout: ` Warning: first warning @@ -880,7 +880,7 @@ up manually: addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -893,7 +893,7 @@ up manually: addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) state.SetResourceInstanceDeposed( addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -907,7 +907,7 @@ up manually: addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) }), stdout: ` Warning: first warning @@ -954,7 +954,7 @@ up manually: addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("null"), - }) + }, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -978,7 +978,7 @@ up manually: addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("null"), - }) + }, addrs.NoKey) }), stdout: ` Warning: first warning @@ -1096,7 +1096,7 @@ OpenTofu was in the process of creating the following resources for }, }, &states.ResourceInstanceObjectSrc{}, - addrs.AbsProviderConfig{}) + addrs.AbsProviderConfig{}, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.AbsResourceInstance{ @@ -1110,7 +1110,7 @@ OpenTofu was in the process of creating the following resources for }, }, &states.ResourceInstanceObjectSrc{}, - addrs.AbsProviderConfig{}) + addrs.AbsProviderConfig{}, addrs.NoKey) }), }, created: nil, @@ -1146,7 +1146,7 @@ test: }, }, &states.ResourceInstanceObjectSrc{}, - addrs.AbsProviderConfig{}) + addrs.AbsProviderConfig{}, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.AbsResourceInstance{ @@ -1160,7 +1160,7 @@ test: }, }, &states.ResourceInstanceObjectSrc{}, - addrs.AbsProviderConfig{}) + addrs.AbsProviderConfig{}, addrs.NoKey) }), }, created: nil, @@ -1196,7 +1196,7 @@ OpenTofu has already created the following resources for "setup_block" from }, }, &states.ResourceInstanceObjectSrc{}, - addrs.AbsProviderConfig{}) + addrs.AbsProviderConfig{}, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.AbsResourceInstance{ @@ -1210,7 +1210,7 @@ OpenTofu has already created the following resources for "setup_block" from }, }, &states.ResourceInstanceObjectSrc{}, - addrs.AbsProviderConfig{}) + addrs.AbsProviderConfig{}, addrs.NoKey) }), nil: states.BuildState(func(state *states.SyncState) { state.SetResourceInstanceCurrent( @@ -1225,7 +1225,7 @@ OpenTofu has already created the following resources for "setup_block" from }, }, &states.ResourceInstanceObjectSrc{}, - addrs.AbsProviderConfig{}) + addrs.AbsProviderConfig{}, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.AbsResourceInstance{ @@ -1239,7 +1239,7 @@ OpenTofu has already created the following resources for "setup_block" from }, }, &states.ResourceInstanceObjectSrc{}, - addrs.AbsProviderConfig{}) + addrs.AbsProviderConfig{}, addrs.NoKey) }), }, created: []*plans.ResourceInstanceChangeSrc{ @@ -2021,7 +2021,7 @@ func TestTestJSON_DestroySummary(t *testing.T) { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) }), want: []map[string]interface{}{ { @@ -2060,7 +2060,7 @@ func TestTestJSON_DestroySummary(t *testing.T) { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -2073,7 +2073,7 @@ func TestTestJSON_DestroySummary(t *testing.T) { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) state.SetResourceInstanceDeposed( addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -2087,7 +2087,7 @@ func TestTestJSON_DestroySummary(t *testing.T) { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) }), want: []map[string]interface{}{ { @@ -2157,7 +2157,7 @@ func TestTestJSON_DestroySummary(t *testing.T) { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -2170,7 +2170,7 @@ func TestTestJSON_DestroySummary(t *testing.T) { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) state.SetResourceInstanceDeposed( addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -2184,7 +2184,7 @@ func TestTestJSON_DestroySummary(t *testing.T) { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) }), want: []map[string]interface{}{ { @@ -2266,7 +2266,7 @@ func TestTestJSON_DestroySummary(t *testing.T) { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("null"), - }) + }, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -2290,7 +2290,7 @@ func TestTestJSON_DestroySummary(t *testing.T) { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("null"), - }) + }, addrs.NoKey) }), want: []map[string]interface{}{ { "@level": "error", @@ -2864,7 +2864,7 @@ func TestTestJSON_Run(t *testing.T) { Namespace: "hashicorp", Type: "test", }, - }) + }, addrs.NoKey) }), Config: &configs.Config{ Module: &configs.Module{}, @@ -3021,7 +3021,7 @@ func TestTestJSON_FatalInterruptSummary(t *testing.T) { }, }, &states.ResourceInstanceObjectSrc{}, - addrs.AbsProviderConfig{}) + addrs.AbsProviderConfig{}, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.AbsResourceInstance{ @@ -3035,7 +3035,7 @@ func TestTestJSON_FatalInterruptSummary(t *testing.T) { }, }, &states.ResourceInstanceObjectSrc{}, - addrs.AbsProviderConfig{}) + addrs.AbsProviderConfig{}, addrs.NoKey) }), }, changes: nil, @@ -3074,7 +3074,7 @@ func TestTestJSON_FatalInterruptSummary(t *testing.T) { }, }, &states.ResourceInstanceObjectSrc{}, - addrs.AbsProviderConfig{}) + addrs.AbsProviderConfig{}, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.AbsResourceInstance{ @@ -3088,7 +3088,7 @@ func TestTestJSON_FatalInterruptSummary(t *testing.T) { }, }, &states.ResourceInstanceObjectSrc{}, - addrs.AbsProviderConfig{}) + addrs.AbsProviderConfig{}, addrs.NoKey) }), }, changes: nil, @@ -3129,7 +3129,7 @@ func TestTestJSON_FatalInterruptSummary(t *testing.T) { }, }, &states.ResourceInstanceObjectSrc{}, - addrs.AbsProviderConfig{}) + addrs.AbsProviderConfig{}, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.AbsResourceInstance{ @@ -3143,7 +3143,7 @@ func TestTestJSON_FatalInterruptSummary(t *testing.T) { }, }, &states.ResourceInstanceObjectSrc{}, - addrs.AbsProviderConfig{}) + addrs.AbsProviderConfig{}, addrs.NoKey) }), nil: states.BuildState(func(state *states.SyncState) { state.SetResourceInstanceCurrent( @@ -3158,7 +3158,7 @@ func TestTestJSON_FatalInterruptSummary(t *testing.T) { }, }, &states.ResourceInstanceObjectSrc{}, - addrs.AbsProviderConfig{}) + addrs.AbsProviderConfig{}, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.AbsResourceInstance{ @@ -3172,7 +3172,7 @@ func TestTestJSON_FatalInterruptSummary(t *testing.T) { }, }, &states.ResourceInstanceObjectSrc{}, - addrs.AbsProviderConfig{}) + addrs.AbsProviderConfig{}, addrs.NoKey) }), }, changes: []*plans.ResourceInstanceChangeSrc{ @@ -3279,7 +3279,7 @@ func TestSaveErroredStateFile(t *testing.T) { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -3292,7 +3292,7 @@ func TestSaveErroredStateFile(t *testing.T) { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) state.SetResourceInstanceDeposed( addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -3306,7 +3306,7 @@ func TestSaveErroredStateFile(t *testing.T) { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) }), stderr: ` Writing state to file: errored_test.tfstate @@ -3328,7 +3328,7 @@ Writing state to file: errored_test.tfstate addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("null"), - }) + }, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -3352,7 +3352,7 @@ Writing state to file: errored_test.tfstate addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("null"), - }) + }, addrs.NoKey) }), stderr: ` Writing state to file: errored_test.tfstate @@ -3384,7 +3384,7 @@ Writing state to file: errored_test.tfstate addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) }), stderr: "", want: []map[string]interface{}{ @@ -3410,7 +3410,7 @@ Writing state to file: errored_test.tfstate addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -3423,7 +3423,7 @@ Writing state to file: errored_test.tfstate addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) state.SetResourceInstanceDeposed( addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -3437,7 +3437,7 @@ Writing state to file: errored_test.tfstate addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) }), stderr: "", want: []map[string]interface{}{ @@ -3463,7 +3463,7 @@ Writing state to file: errored_test.tfstate addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("null"), - }) + }, addrs.NoKey) state.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -3487,7 +3487,7 @@ Writing state to file: errored_test.tfstate addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("null"), - }) + }, addrs.NoKey) }), stderr: "", want: []map[string]interface{}{ diff --git a/internal/command/workspace_command_test.go b/internal/command/workspace_command_test.go index c9541ab2bd..aa40748aa4 100644 --- a/internal/command/workspace_command_test.go +++ b/internal/command/workspace_command_test.go @@ -251,6 +251,7 @@ func TestWorkspace_createWithState(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) diff --git a/internal/configs/module.go b/internal/configs/module.go index 7a11554dc5..c1ba5ec42a 100644 --- a/internal/configs/module.go +++ b/internal/configs/module.go @@ -250,6 +250,11 @@ func NewModule(primaryFiles, overrideFiles []*File, call StaticModuleCall, sourc diags = append(diags, mDiags...) } + for _, pc := range mod.ProviderConfigs { + pDiags := pc.decodeStaticFields(mod.StaticEvaluator) + diags = append(diags, pDiags...) + } + diags = append(diags, checkModuleExperiments(mod)...) // Generate the FQN -> LocalProviderName map diff --git a/internal/configs/parser_config_dir_test.go b/internal/configs/parser_config_dir_test.go index d4780ed860..87f123973d 100644 --- a/internal/configs/parser_config_dir_test.go +++ b/internal/configs/parser_config_dir_test.go @@ -95,7 +95,7 @@ func TestParserLoadConfigDirSuccess(t *testing.T) { for _, info := range files { name := info.Name() - t.Run(fmt.Sprintf("%s as module", name), func(t *testing.T) { + t.Run(name, func(t *testing.T) { src, err := os.ReadFile(filepath.Join("testdata/valid-files", name)) if err != nil { t.Fatal(err) diff --git a/internal/configs/provider.go b/internal/configs/provider.go index 43aa12d6de..333a55d017 100644 --- a/internal/configs/provider.go +++ b/internal/configs/provider.go @@ -11,8 +11,11 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/gohcl" "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/zclconf/go-cty/cty" "github.com/opentofu/opentofu/internal/addrs" + "github.com/opentofu/opentofu/internal/instances" + "github.com/opentofu/opentofu/internal/lang/evalchecks" "github.com/opentofu/opentofu/internal/tfdiags" ) @@ -42,6 +45,9 @@ type Provider struct { // testing framework to instantiate test provider wrapper. IsMocked bool MockResources []*MockResource + + ForEach hcl.Expression + Instances map[addrs.InstanceKey]instances.RepetitionData } func decodeProviderBlock(block *hcl.Block) (*Provider, hcl.Diagnostics) { @@ -95,8 +101,21 @@ func decodeProviderBlock(block *hcl.Block) (*Provider, hcl.Diagnostics) { diags = append(diags, versionDiags...) } + if attr, exists := content.Attributes["for_each"]; exists { + provider.ForEach = attr.Expr + } + + if len(provider.Alias) == 0 && provider.ForEach != nil { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: `Alias required when using "for_each"`, + Detail: `The for_each argument is allowed only for provider configurations with an alias.`, + Subject: provider.ForEach.Range().Ptr(), + }) + } + // Reserved attribute names - for _, name := range []string{"count", "depends_on", "for_each", "source"} { + for _, name := range []string{"count", "depends_on", "source"} { if attr, exists := content.Attributes[name]; exists { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, @@ -145,6 +164,38 @@ func decodeProviderBlock(block *hcl.Block) (*Provider, hcl.Diagnostics) { return provider, diags } +func (p *Provider) decodeStaticFields(eval *StaticEvaluator) hcl.Diagnostics { + var diags hcl.Diagnostics + + if p.ForEach != nil { + forEachRefsFunc := func(refs []*addrs.Reference) (*hcl.EvalContext, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + evalContext, evalDiags := eval.EvalContext(StaticIdentifier{ + Module: eval.call.addr, + Subject: fmt.Sprintf("provider.%s.%s.for_each", p.Name, p.Alias), + DeclRange: p.ForEach.Range(), + }, refs) + return evalContext, diags.Append(evalDiags) + } + + forVal, evalDiags := evalchecks.EvaluateForEachExpression(p.ForEach, forEachRefsFunc) + diags = append(diags, evalDiags.ToHCL()...) + if evalDiags.HasErrors() { + return diags + } + + p.Instances = make(map[addrs.InstanceKey]instances.RepetitionData) + for k, v := range forVal { + p.Instances[addrs.StringKey(k)] = instances.RepetitionData{ + EachKey: cty.StringVal(k), + EachValue: v, + } + } + } + + return diags +} + // Addr returns the address of the receiving provider configuration, relative // to its containing module. func (p *Provider) Addr() addrs.LocalProviderConfig { @@ -247,11 +298,13 @@ var providerBlockSchema = &hcl.BodySchema{ { Name: "version", }, + { + Name: "for_each", + }, // Attribute names reserved for future expansion. {Name: "count"}, {Name: "depends_on"}, - {Name: "for_each"}, {Name: "source"}, }, Blocks: []hcl.BlockHeaderSchema{ diff --git a/internal/configs/provider_test.go b/internal/configs/provider_test.go index 6f09abfa86..ea0e8fc83d 100644 --- a/internal/configs/provider_test.go +++ b/internal/configs/provider_test.go @@ -30,10 +30,9 @@ func TestProviderReservedNames(t *testing.T) { `config.tf:4,13-20: Version constraints inside provider configuration blocks are deprecated; OpenTofu 0.13 and earlier allowed provider version constraints inside the provider configuration block, but that is now deprecated and will be removed in a future version of OpenTofu. To silence this warning, move the provider version constraint into the required_providers block.`, `config.tf:10,3-8: Reserved argument name in provider block; The provider argument name "count" is reserved for use by OpenTofu in a future version.`, `config.tf:11,3-13: Reserved argument name in provider block; The provider argument name "depends_on" is reserved for use by OpenTofu in a future version.`, - `config.tf:12,3-11: Reserved argument name in provider block; The provider argument name "for_each" is reserved for use by OpenTofu in a future version.`, - `config.tf:14,3-12: Reserved block type name in provider block; The block type name "lifecycle" is reserved for use by OpenTofu in a future version.`, - `config.tf:15,3-9: Reserved block type name in provider block; The block type name "locals" is reserved for use by OpenTofu in a future version.`, - `config.tf:13,3-9: Reserved argument name in provider block; The provider argument name "source" is reserved for use by OpenTofu in a future version.`, + `config.tf:12,3-9: Reserved argument name in provider block; The provider argument name "source" is reserved for use by OpenTofu in a future version.`, + `config.tf:13,3-12: Reserved block type name in provider block; The block type name "lifecycle" is reserved for use by OpenTofu in a future version.`, + `config.tf:14,3-9: Reserved block type name in provider block; The block type name "locals" is reserved for use by OpenTofu in a future version.`, }) } diff --git a/internal/configs/provider_validation.go b/internal/configs/provider_validation.go index 094b08a58f..e82478256c 100644 --- a/internal/configs/provider_validation.go +++ b/internal/configs/provider_validation.go @@ -322,6 +322,8 @@ func validateProviderConfigs(parentCall *ModuleCall, cfg *Config, noProviderConf // of the configuration block declaration. configured := map[string]hcl.Range{} + instanced := map[string]bool{} + // the set of configuration_aliases defined in the required_providers // block, with the fully qualified provider type. configAliases := map[string]addrs.AbsProviderConfig{} @@ -339,6 +341,8 @@ func validateProviderConfigs(parentCall *ModuleCall, cfg *Config, noProviderConf } else { emptyConfigs[name] = pc.DeclRange } + + instanced[name] = len(pc.Instances) != 0 } if mod.ProviderRequirements != nil { @@ -470,6 +474,37 @@ func validateProviderConfigs(parentCall *ModuleCall, cfg *Config, noProviderConf } } + // Validate provider expansion is properly configured + for _, modCall := range mod.ModuleCalls { + for _, passed := range modCall.Providers { + if passed.InChild.KeyExpression != nil { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid module provider configuration", + Detail: "Instance keys are not allowed on the left side of a provider configuration assignment.", + Subject: passed.InChild.KeyExpression.Range().Ptr(), + }) + } + + isInstanced := instanced[providerName(passed.InParent.Name, passed.InParent.Alias)] + diags = diags.Extend(passed.InParent.InstanceValidation("module", isInstanced)) + } + } + // Validate that resources using provider keys are properly configured + checkProviderKeys := func(resourceConfigs map[string]*Resource) { + for _, r := range resourceConfigs { + // We're looking for resources with a specific provider reference + if r.ProviderConfigRef == nil { + continue + } + + isInstanced := instanced[providerName(r.ProviderConfigRef.Name, r.ProviderConfigRef.Alias)] + diags = diags.Extend(r.ProviderConfigRef.InstanceValidation("resource", isInstanced)) + } + } + checkProviderKeys(mod.ManagedResources) + checkProviderKeys(mod.DataResources) + // Verify that any module calls only refer to named providers, and that // those providers will have a configuration at runtime. This way we can // direct users where to add the missing configuration, because the runtime diff --git a/internal/configs/resource.go b/internal/configs/resource.go index 50e5cb7308..be91c0ad15 100644 --- a/internal/configs/resource.go +++ b/internal/configs/resource.go @@ -659,11 +659,31 @@ type ProviderConfigRef struct { // export this so providers don't need to be re-resolved. // This same field is also added to the Provider struct. providerType addrs.Provider + + KeyExpression hcl.Expression } func decodeProviderConfigRef(expr hcl.Expression, argName string) (*ProviderConfigRef, hcl.Diagnostics) { var diags hcl.Diagnostics + var keyExpr hcl.Expression + + const ( + // name.alias[const_key] + nameIndex = 0 + aliasIndex = 1 + keyIndex = 2 + ) + var maxTraversalLength = keyIndex + 1 + + // name.alias[expr_key] + if iex, ok := expr.(*hclsyntax.IndexExpr); ok { + maxTraversalLength = aliasIndex + 1 // expr key found, no const key allowed + + keyExpr = iex.Key + expr = iex.Collection + } + var shimDiags hcl.Diagnostics expr, shimDiags = shimTraversalInString(expr, false) diags = append(diags, shimDiags...) @@ -677,7 +697,7 @@ func decodeProviderConfigRef(expr hcl.Expression, argName string) (*ProviderConf diags = append(diags, travDiags...) } - if len(traversal) < 1 || len(traversal) > 2 { + if len(traversal) == 0 || len(traversal) > maxTraversalLength { // A provider reference was given as a string literal in the legacy // configuration language and there are lots of examples out there // showing that usage, so we'll sniff for that situation here and @@ -696,7 +716,7 @@ func decodeProviderConfigRef(expr hcl.Expression, argName string) (*ProviderConf diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid provider configuration reference", - Detail: fmt.Sprintf("The %s argument requires a provider type name, optionally followed by a period and then a configuration alias.", argName), + Detail: fmt.Sprintf("The %s argument requires a provider type name, optionally followed by a period and then a configuration alias and optional instance key.", argName), Subject: expr.Range().Ptr(), }) return nil, diags @@ -704,25 +724,26 @@ func decodeProviderConfigRef(expr hcl.Expression, argName string) (*ProviderConf // verify that the provider local name is normalized name := traversal.RootName() - nameDiags := checkProviderNameNormalized(name, traversal[0].SourceRange()) + nameDiags := checkProviderNameNormalized(name, traversal[nameIndex].SourceRange()) diags = append(diags, nameDiags...) if diags.HasErrors() { return nil, diags } ret := &ProviderConfigRef{ - Name: name, - NameRange: traversal[0].SourceRange(), + Name: name, + NameRange: traversal[nameIndex].SourceRange(), + KeyExpression: keyExpr, } - if len(traversal) > 1 { - aliasStep, ok := traversal[1].(hcl.TraverseAttr) + if len(traversal) > aliasIndex { + aliasStep, ok := traversal[aliasIndex].(hcl.TraverseAttr) if !ok { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid provider configuration reference", Detail: "Provider name must either stand alone or be followed by a period and then a configuration alias.", - Subject: traversal[1].SourceRange().Ptr(), + Subject: traversal[aliasIndex].SourceRange().Ptr(), }) return ret, diags } @@ -731,6 +752,30 @@ func decodeProviderConfigRef(expr hcl.Expression, argName string) (*ProviderConf ret.AliasRange = aliasStep.SourceRange().Ptr() } + if len(traversal) > keyIndex { + indexStep, ok := traversal[keyIndex].(hcl.TraverseIndex) + if !ok { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid provider configuration reference", + Detail: "Provider name must either stand alone or be followed by a period and then a configuration alias.", + Subject: traversal[keyIndex].SourceRange().Ptr(), + }) + return ret, diags + } + + ret.KeyExpression = hcl.StaticExpr(indexStep.Key, traversal.SourceRange()) + } + + if len(ret.Alias) == 0 && ret.KeyExpression != nil { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid provider configuration reference", + Detail: "Provider assignment requires an alias when specifying an instance key, in the form of provider.name[instance_key]", + Subject: traversal.SourceRange().Ptr(), + }) + } + return ret, diags } @@ -756,6 +801,39 @@ func (r *ProviderConfigRef) String() string { return r.Name } +func (r *ProviderConfigRef) InstanceValidation(blockType string, isInstanced bool) hcl.Diagnostics { + var diags hcl.Diagnostics + + summary := fmt.Sprintf("Invalid %s provider configuration", blockType) + + if r.KeyExpression != nil { + if r.Alias == "" { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: summary, + Detail: "An instance key can be specified only for a provider configuration which has an alias and uses for_each.", + Subject: r.KeyExpression.Range().Ptr(), + }) + } + if !isInstanced { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: summary, + Detail: "An instance key can be specified only for a provider configuration that uses for_each.", + Subject: r.KeyExpression.Range().Ptr(), + }) + } + } else if isInstanced { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: summary, + Detail: "A reference to a provider configuration which uses for_each requires an instance key. Passing a collection of provider instances into a child module is not allowed.", + Subject: r.NameRange.Ptr(), + }) + } + return diags +} + var commonResourceAttributes = []hcl.AttributeSchema{ { Name: "count", diff --git a/internal/configs/static_evaluator_test.go b/internal/configs/static_evaluator_test.go index f10658533c..1c83038f7f 100644 --- a/internal/configs/static_evaluator_test.go +++ b/internal/configs/static_evaluator_test.go @@ -16,8 +16,6 @@ import ( ) // This exercises most of the logic in StaticEvaluator and staticScopeData -// -//nolint:cyclop // it's a test func TestStaticEvaluator_Evaluate(t *testing.T) { // Synthetic file for building test components testData := ` diff --git a/internal/configs/testdata/invalid-files/provider-reserved.tf b/internal/configs/testdata/invalid-files/provider-reserved.tf index 559474de99..38681418b3 100644 --- a/internal/configs/testdata/invalid-files/provider-reserved.tf +++ b/internal/configs/testdata/invalid-files/provider-reserved.tf @@ -7,9 +7,8 @@ provider "test" { arbitrary = true # These are all reserved and should generate errors. - count = 3 + count = 3 depends_on = ["foo.bar"] - for_each = ["a", "b"] source = "foo.example.com/baz/bar" lifecycle {} locals {} diff --git a/internal/configs/testdata/valid-files/provider-configs.tf b/internal/configs/testdata/valid-files/provider-configs.tf index 6077d64118..41cf687c4a 100644 --- a/internal/configs/testdata/valid-files/provider-configs.tf +++ b/internal/configs/testdata/valid-files/provider-configs.tf @@ -11,3 +11,8 @@ provider "bar" { alias = "bar" } + +provider "baz" { + alias = "foo" + for_each = {"a": "first", "b": "second"} +} diff --git a/internal/refactoring/move_execute_test.go b/internal/refactoring/move_execute_test.go index 8a2e4cd048..8b4089d60f 100644 --- a/internal/refactoring/move_execute_test.go +++ b/internal/refactoring/move_execute_test.go @@ -59,6 +59,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) }), emptyResults, @@ -78,6 +79,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) }), MoveResults{ @@ -105,6 +107,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) }), MoveResults{ @@ -133,6 +136,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) }), MoveResults{ @@ -161,6 +165,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) }), MoveResults{ @@ -189,6 +194,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) }), MoveResults{ @@ -217,6 +223,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) s.SetResourceInstanceCurrent( mustParseInstAddr("module.boo.module.hoo.foo.from"), @@ -225,6 +232,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) }), MoveResults{ @@ -258,6 +266,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) }), MoveResults{ @@ -287,6 +296,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) }), MoveResults{ @@ -316,6 +326,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) }), MoveResults{ @@ -344,6 +355,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) s.SetResourceInstanceCurrent( mustParseInstAddr("module.boo.foo.to[0]"), @@ -352,6 +364,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) }), MoveResults{ @@ -386,6 +399,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) s.SetResourceInstanceCurrent( mustParseInstAddr("foo.to"), @@ -394,6 +408,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) }), MoveResults{ @@ -428,6 +443,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) s.SetResourceInstanceCurrent( mustParseInstAddr("foo.to[0]"), @@ -436,6 +452,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) }), MoveResults{ @@ -470,6 +487,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) }), MoveResults{ @@ -499,6 +517,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) s.SetResourceInstanceCurrent( mustParseInstAddr("foo.from"), @@ -507,6 +526,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) }), MoveResults{ @@ -542,6 +562,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) s.SetResourceInstanceCurrent( mustParseInstAddr("bar.from"), @@ -550,6 +571,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) }), MoveResults{ @@ -584,6 +606,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) s.SetResourceInstanceCurrent( mustParseInstAddr("foo.from"), @@ -592,6 +615,7 @@ func TestApplyMoves(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerAddr, + addrs.NoKey, ) }), MoveResults{ diff --git a/internal/refactoring/move_statement_test.go b/internal/refactoring/move_statement_test.go index 632e67dcd9..1c500df00d 100644 --- a/internal/refactoring/move_statement_test.go +++ b/internal/refactoring/move_statement_test.go @@ -99,41 +99,49 @@ func TestImpliedMoveStatements(t *testing.T) { resourceAddr("formerly_count").Instance(addrs.IntKey(0)), instObjState(), providerAddr, + addrs.NoKey, ) s.SetResourceInstanceCurrent( resourceAddr("formerly_count").Instance(addrs.IntKey(1)), instObjState(), providerAddr, + addrs.NoKey, ) s.SetResourceInstanceCurrent( resourceAddr("now_count").Instance(addrs.NoKey), instObjState(), providerAddr, + addrs.NoKey, ) s.SetResourceInstanceCurrent( resourceAddr("formerly_count_explicit").Instance(addrs.IntKey(0)), instObjState(), providerAddr, + addrs.NoKey, ) s.SetResourceInstanceCurrent( resourceAddr("formerly_count_explicit").Instance(addrs.IntKey(1)), instObjState(), providerAddr, + addrs.NoKey, ) s.SetResourceInstanceCurrent( resourceAddr("now_count_explicit").Instance(addrs.NoKey), instObjState(), providerAddr, + addrs.NoKey, ) s.SetResourceInstanceCurrent( resourceAddr("now_for_each_formerly_count").Instance(addrs.IntKey(0)), instObjState(), providerAddr, + addrs.NoKey, ) s.SetResourceInstanceCurrent( resourceAddr("now_for_each_formerly_no_count").Instance(addrs.NoKey), instObjState(), providerAddr, + addrs.NoKey, ) // This "ambiguous" resource is representing a rare but possible @@ -147,11 +155,13 @@ func TestImpliedMoveStatements(t *testing.T) { resourceAddr("ambiguous").Instance(addrs.NoKey), instObjState(), providerAddr, + addrs.NoKey, ) s.SetResourceInstanceCurrent( resourceAddr("ambiguous").Instance(addrs.IntKey(0)), instObjState(), providerAddr, + addrs.NoKey, ) // Add two resource nested in a module to ensure we find these @@ -160,11 +170,13 @@ func TestImpliedMoveStatements(t *testing.T) { nestedResourceAddr("child", "formerly_count").Instance(addrs.IntKey(0)), instObjState(), providerAddr, + addrs.NoKey, ) s.SetResourceInstanceCurrent( nestedResourceAddr("child", "now_count").Instance(addrs.NoKey), instObjState(), providerAddr, + addrs.NoKey, ) }) diff --git a/internal/repl/session_test.go b/internal/repl/session_test.go index a15c3d0c8c..b3ac0fafd0 100644 --- a/internal/repl/session_test.go +++ b/internal/repl/session_test.go @@ -45,6 +45,7 @@ func TestSession_basicState(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -60,6 +61,7 @@ func TestSession_basicState(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) diff --git a/internal/states/module.go b/internal/states/module.go index 199d883e4c..02c32da18f 100644 --- a/internal/states/module.go +++ b/internal/states/module.go @@ -89,7 +89,7 @@ func (ms *Module) RemoveResource(addr addrs.Resource) { // // The provider address is a resource-wide setting and is updated for all other // instances of the same resource as a side-effect of this call. -func (ms *Module) SetResourceInstanceCurrent(addr addrs.ResourceInstance, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) { +func (ms *Module) SetResourceInstanceCurrent(addr addrs.ResourceInstance, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig, providerKey addrs.InstanceKey) { rs := ms.Resource(addr.Resource) // if the resource is nil and the object is nil, don't do anything! // you'll probably just cause issues @@ -139,6 +139,7 @@ func (ms *Module) SetResourceInstanceCurrent(addr addrs.ResourceInstance, obj *R } // Update the resource's ProviderConfig, in case the provider has updated rs.ProviderConfig = provider + is.ProviderKey = providerKey is.Current = obj } @@ -158,11 +159,12 @@ func (ms *Module) SetResourceInstanceCurrent(addr addrs.ResourceInstance, obj *R // is overwritten. Set obj to nil to remove the deposed object altogether. If // the instance is left with no objects after this operation then it will // be removed from its containing resource altogether. -func (ms *Module) SetResourceInstanceDeposed(addr addrs.ResourceInstance, key DeposedKey, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) { +func (ms *Module) SetResourceInstanceDeposed(addr addrs.ResourceInstance, key DeposedKey, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig, providerKey addrs.InstanceKey) { ms.SetResourceProvider(addr.Resource, provider) rs := ms.Resource(addr.Resource) is := rs.EnsureInstance(addr.Key) + is.ProviderKey = providerKey if obj != nil { is.Deposed[key] = obj } else { diff --git a/internal/states/remote/state_test.go b/internal/states/remote/state_test.go index d77b1138e8..a35bbca94e 100644 --- a/internal/states/remote/state_test.go +++ b/internal/states/remote/state_test.go @@ -99,6 +99,7 @@ func TestStatePersist(t *testing.T) { addrs.AbsProviderConfig{ Provider: tfaddr.Provider{Namespace: "local"}, }, + addrs.NoKey, ) return s, func() {} }, diff --git a/internal/states/resource.go b/internal/states/resource.go index 01faf6bf41..3fe76734c5 100644 --- a/internal/states/resource.go +++ b/internal/states/resource.go @@ -70,6 +70,11 @@ type ResourceInstance struct { // replaced and are pending destruction due to the create_before_destroy // lifecycle mode. Deposed map[DeposedKey]*ResourceInstanceObjectSrc + + // ProviderKey, in combination with Resource.ProviderConfig, represents + // the resource instance's provider configuration. This is only set + // when using provider iteration on resources or modules + ProviderKey addrs.InstanceKey } // NewResourceInstance constructs and returns a new ResourceInstance, ready to diff --git a/internal/states/state_deepcopy.go b/internal/states/state_deepcopy.go index c634c37f8f..908956a2c6 100644 --- a/internal/states/state_deepcopy.go +++ b/internal/states/state_deepcopy.go @@ -118,8 +118,9 @@ func (i *ResourceInstance) DeepCopy() *ResourceInstance { } return &ResourceInstance{ - Current: i.Current.DeepCopy(), - Deposed: deposed, + Current: i.Current.DeepCopy(), + Deposed: deposed, + ProviderKey: i.ProviderKey, } } diff --git a/internal/states/state_test.go b/internal/states/state_test.go index 1f40721877..cff7c47efd 100644 --- a/internal/states/state_test.go +++ b/internal/states/state_test.go @@ -47,6 +47,7 @@ func TestState(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) childModule := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) @@ -254,6 +255,7 @@ func TestStateDeepCopy(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) rootModule.SetResourceInstanceCurrent( addrs.Resource{ @@ -288,6 +290,7 @@ func TestStateDeepCopy(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) childModule := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) @@ -326,6 +329,7 @@ func TestStateHasResourceInstanceObjects(t *testing.T) { Status: ObjectReady, }, providerConfig, + addrs.NoKey, ) }, true, @@ -339,6 +343,7 @@ func TestStateHasResourceInstanceObjects(t *testing.T) { Status: ObjectReady, }, childModuleProviderConfig, + addrs.NoKey, ) }, true, @@ -352,6 +357,7 @@ func TestStateHasResourceInstanceObjects(t *testing.T) { Status: ObjectTainted, }, providerConfig, + addrs.NoKey, ) }, true, @@ -366,6 +372,7 @@ func TestStateHasResourceInstanceObjects(t *testing.T) { Status: ObjectTainted, }, providerConfig, + addrs.NoKey, ) }, true, @@ -384,6 +391,7 @@ func TestStateHasResourceInstanceObjects(t *testing.T) { Status: ObjectTainted, }, providerConfig, + addrs.NoKey, ) s := ss.Lock() delete(s.Modules[""].Resources["test.foo"].Instances, addrs.NoKey) @@ -400,6 +408,7 @@ func TestStateHasResourceInstanceObjects(t *testing.T) { Status: ObjectReady, }, providerConfig, + addrs.NoKey, ) }, false, // data resources aren't managed resources, so they don't count @@ -437,6 +446,7 @@ func TestState_MoveAbsResource(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Absolute(addrs.RootModuleInstance) @@ -504,6 +514,7 @@ func TestState_MoveAbsResource(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) cm.SetResourceInstanceCurrent( addrs.Resource{ @@ -520,6 +531,7 @@ func TestState_MoveAbsResource(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule) @@ -569,6 +581,7 @@ func TestState_MoveAbsResource(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule) @@ -615,6 +628,7 @@ func TestState_MoveAbsResource(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule) @@ -660,6 +674,7 @@ func TestState_MaybeMoveAbsResource(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Absolute(addrs.RootModuleInstance) @@ -700,6 +715,7 @@ func TestState_MoveAbsResourceInstance(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) // src resource from the state above src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) @@ -770,6 +786,7 @@ func TestState_MaybeMoveAbsResourceInstance(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) // For a little extra fun, let's go from a resource to a resource instance: test_thing.foo to test_thing.bar[1] @@ -816,6 +833,7 @@ func TestState_MoveModuleInstance(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) dstModule := addrs.RootModuleInstance.Child("child", addrs.IntKey(3)) @@ -863,6 +881,7 @@ func TestState_MaybeMoveModuleInstance(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) dst := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("b")) @@ -905,6 +924,7 @@ func TestState_MoveModule(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) moduleInstance := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("a")) @@ -924,6 +944,7 @@ func TestState_MoveModule(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) _, mc := srcModule.Call() @@ -977,6 +998,7 @@ func TestState_MoveModule(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) _, dstMC := addrs.RootModule.Child("child").Call() diff --git a/internal/states/statefile/version4.go b/internal/states/statefile/version4.go index f1aaecc3e2..e80bc5a0d3 100644 --- a/internal/states/statefile/version4.go +++ b/internal/states/statefile/version4.go @@ -90,18 +90,29 @@ func prepareStateV4(sV4 *stateV4) (*File, tfdiags.Diagnostics) { } } - providerAddr, addrDiags := addrs.ParseAbsProviderConfigStr(rsV4.ProviderConfig) - diags.Append(addrDiags) - if addrDiags.HasErrors() { - // If ParseAbsProviderConfigStr returns an error, the state may have - // been written before Provider FQNs were introduced and the - // AbsProviderConfig string format will need normalization. If so, - // we treat it like a legacy provider (namespace "-") and let the - // provider installer handle detecting the FQN. - var legacyAddrDiags tfdiags.Diagnostics - providerAddr, legacyAddrDiags = addrs.ParseLegacyAbsProviderConfigStr(rsV4.ProviderConfig) - if legacyAddrDiags.HasErrors() { - continue + var providerAddr addrs.AbsProviderConfig + var addrDiags tfdiags.Diagnostics + if rsV4.ProviderConfig != "" { + providerAddr, addrDiags = addrs.ParseAbsProviderConfigStr(rsV4.ProviderConfig) + if addrDiags.HasErrors() { + // If ParseAbsProviderConfigStr returns an error, the state may have + // been written before Provider FQNs were introduced and the + // AbsProviderConfig string format will need normalization. If so, + // we treat it like a legacy provider (namespace "-") and let the + // provider installer handle detecting the FQN. + var legacyAddrDiags tfdiags.Diagnostics + providerAddr, legacyAddrDiags = addrs.ParseLegacyAbsProviderConfigStr(rsV4.ProviderConfig) + if legacyAddrDiags.HasErrors() { + // Neither parse formats are valid, let's report the original error + diags = diags.Append(addrDiags) + continue + } + + // Valid legacy address, but may contain warnings + diags = diags.Append(legacyAddrDiags) + } else { + // Valid address, but may contain warnings + diags = diags.Append(addrDiags) } } @@ -110,6 +121,9 @@ func prepareStateV4(sV4 *stateV4) (*File, tfdiags.Diagnostics) { // Ensure the resource container object is present in the state. ms.SetResourceProvider(rAddr, providerAddr) + // Keep track of instance providers for validation + var instanceProviders []addrs.AbsProviderConfig + for _, isV4 := range rsV4.Instances { keyRaw := isV4.IndexKey var key addrs.InstanceKey @@ -137,6 +151,30 @@ func prepareStateV4(sV4 *stateV4) (*File, tfdiags.Diagnostics) { key = addrs.NoKey } + if isV4.ProviderConfig != "" && rsV4.ProviderConfig != "" { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Warning, + "Provider field conflict in state", + fmt.Sprintf("Resource %s has a provider address %s, as well as instance %s with provider address %s.", rAddr.Absolute(moduleAddr), rsV4.ProviderConfig, key, isV4.ProviderConfig), + )) + } + + if isV4.ProviderConfig == "" && rsV4.ProviderConfig == "" { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Provider field missing state", + fmt.Sprintf("Resource %s is missing a provider address, both on the resource and the resource instances.", rAddr.Absolute(moduleAddr)), + )) + } + + instanceProvider := providerAddr + instanceProviderKey := addrs.NoKey + if isV4.ProviderConfig != "" { + instanceProvider, instanceProviderKey, addrDiags = addrs.ParseAbsProviderConfigInstanceStr(isV4.ProviderConfig) + diags = diags.Append(addrDiags) + instanceProviders = append(instanceProviders, instanceProvider) + } + instAddr := rAddr.Instance(key) obj := &states.ResourceInstanceObjectSrc{ @@ -235,7 +273,7 @@ func prepareStateV4(sV4 *stateV4) (*File, tfdiags.Diagnostics) { continue } - ms.SetResourceInstanceDeposed(instAddr, dk, obj, providerAddr) + ms.SetResourceInstanceDeposed(instAddr, dk, obj, instanceProvider, instanceProviderKey) default: is := ms.ResourceInstance(instAddr) if is.HasCurrent() { @@ -247,16 +285,21 @@ func prepareStateV4(sV4 *stateV4) (*File, tfdiags.Diagnostics) { continue } - ms.SetResourceInstanceCurrent(instAddr, obj, providerAddr) + ms.SetResourceInstanceCurrent(instAddr, obj, instanceProvider, instanceProviderKey) } } - // We repeat this after creating the instances because - // SetResourceInstanceCurrent automatically resets this metadata based - // on the incoming objects. That behavior is useful when we're making - // piecemeal updates to the state during an apply, but when we're - // reading the state file we want to reflect its contents exactly. - ms.SetResourceProvider(rAddr, providerAddr) + // Validate instance providers + for i := 1; i < len(instanceProviders); i++ { + if instanceProviders[i-1].String() != instanceProviders[i].String() { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Provider instance field conflict in state", + fmt.Sprintf("Resource %s has instances with different provider addresses: %q != %q.", rAddr.Absolute(moduleAddr), instanceProviders[i-1], instanceProviders[i]), + )) + break + } + } } // The root module is special in that we persist its attributes and thus @@ -389,12 +432,25 @@ func writeStateV4(file *File, w io.Writer, enc encryption.StateEncryption) tfdia continue } + hasProviderInstanceKeys := false + for _, is := range rs.Instances { + if is.ProviderKey != addrs.NoKey { + hasProviderInstanceKeys = true + break + } + } + + var providerConfig string + if !hasProviderInstanceKeys { + providerConfig = rs.ProviderConfig.String() + } + sV4.Resources = append(sV4.Resources, resourceStateV4{ Module: moduleAddr.String(), Mode: mode, Type: resourceAddr.Type, Name: resourceAddr.Name, - ProviderConfig: rs.ProviderConfig.String(), + ProviderConfig: providerConfig, Instances: []instanceObjectStateV4{}, }) rsV4 := &(sV4.Resources[len(sV4.Resources)-1]) @@ -404,7 +460,7 @@ func writeStateV4(file *File, w io.Writer, enc encryption.StateEncryption) tfdia var objDiags tfdiags.Diagnostics rsV4.Instances, objDiags = appendInstanceObjectStateV4( rs, is, key, is.Current, states.NotDeposed, - rsV4.Instances, + rsV4.Instances, hasProviderInstanceKeys, ) diags = diags.Append(objDiags) } @@ -412,7 +468,7 @@ func writeStateV4(file *File, w io.Writer, enc encryption.StateEncryption) tfdia var objDiags tfdiags.Diagnostics rsV4.Instances, objDiags = appendInstanceObjectStateV4( rs, is, key, obj, dk, - rsV4.Instances, + rsV4.Instances, hasProviderInstanceKeys, ) diags = diags.Append(objDiags) } @@ -452,7 +508,7 @@ func writeStateV4(file *File, w io.Writer, enc encryption.StateEncryption) tfdia return diags } -func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstance, key addrs.InstanceKey, obj *states.ResourceInstanceObjectSrc, deposed states.DeposedKey, isV4s []instanceObjectStateV4) ([]instanceObjectStateV4, tfdiags.Diagnostics) { +func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstance, key addrs.InstanceKey, obj *states.ResourceInstanceObjectSrc, deposed states.DeposedKey, isV4s []instanceObjectStateV4, hasProviderInstanceKeys bool) ([]instanceObjectStateV4, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics var status string @@ -495,6 +551,11 @@ func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstanc } } + var providerConfig string + if hasProviderInstanceKeys { + providerConfig = rs.ProviderConfig.InstanceString(is.ProviderKey) + } + // Extract paths from path value marks var paths []cty.Path for _, vm := range obj.AttrSensitivePaths { @@ -509,6 +570,7 @@ func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstanc IndexKey: rawKey, Deposed: string(deposed), Status: status, + ProviderConfig: providerConfig, SchemaVersion: obj.SchemaVersion, AttributesFlat: obj.AttrsFlat, AttributesRaw: obj.AttrsJSON, @@ -704,20 +766,23 @@ type outputStateV4 struct { Sensitive bool `json:"sensitive,omitempty"` } +// Note: the ProviderConfig field is only set on either the resource or the resource instance object +// It should never be set on both type resourceStateV4 struct { Module string `json:"module,omitempty"` Mode string `json:"mode"` Type string `json:"type"` Name string `json:"name"` EachMode string `json:"each,omitempty"` - ProviderConfig string `json:"provider"` + ProviderConfig string `json:"provider,omitempty"` Instances []instanceObjectStateV4 `json:"instances"` } type instanceObjectStateV4 struct { - IndexKey interface{} `json:"index_key,omitempty"` - Status string `json:"status,omitempty"` - Deposed string `json:"deposed,omitempty"` + IndexKey interface{} `json:"index_key,omitempty"` + Status string `json:"status,omitempty"` + Deposed string `json:"deposed,omitempty"` + ProviderConfig string `json:"provider,omitempty"` SchemaVersion uint64 `json:"schema_version"` AttributesRaw json.RawMessage `json:"attributes,omitempty"` diff --git a/internal/states/sync.go b/internal/states/sync.go index 3788a610ce..20af69b174 100644 --- a/internal/states/sync.go +++ b/internal/states/sync.go @@ -273,12 +273,12 @@ func (s *SyncState) RemoveResourceIfEmpty(addr addrs.AbsResource) bool { // // If the containing module for this resource or the resource itself are not // already tracked in state then they will be added as a side-effect. -func (s *SyncState) SetResourceInstanceCurrent(addr addrs.AbsResourceInstance, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) { +func (s *SyncState) SetResourceInstanceCurrent(addr addrs.AbsResourceInstance, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig, providerKey addrs.InstanceKey) { s.lock.Lock() defer s.lock.Unlock() ms := s.state.EnsureModule(addr.Module) - ms.SetResourceInstanceCurrent(addr.Resource, obj.DeepCopy(), provider) + ms.SetResourceInstanceCurrent(addr.Resource, obj.DeepCopy(), provider, providerKey) s.maybePruneModule(addr.Module) } @@ -305,12 +305,12 @@ func (s *SyncState) SetResourceInstanceCurrent(addr addrs.AbsResourceInstance, o // // If the containing module for this resource or the resource itself are not // already tracked in state then they will be added as a side-effect. -func (s *SyncState) SetResourceInstanceDeposed(addr addrs.AbsResourceInstance, key DeposedKey, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) { +func (s *SyncState) SetResourceInstanceDeposed(addr addrs.AbsResourceInstance, key DeposedKey, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig, providerKey addrs.InstanceKey) { s.lock.Lock() defer s.lock.Unlock() ms := s.state.EnsureModule(addr.Module) - ms.SetResourceInstanceDeposed(addr.Resource, key, obj.DeepCopy(), provider) + ms.SetResourceInstanceDeposed(addr.Resource, key, obj.DeepCopy(), provider, providerKey) s.maybePruneModule(addr.Module) } @@ -443,7 +443,7 @@ func (s *SyncState) RemovePlannedResourceInstanceObjects() { if is.Current != nil && is.Current.Status == ObjectPlanned { // Setting the current instance to nil removes it from the // state altogether if there are not also deposed instances. - ms.SetResourceInstanceCurrent(instAddr, nil, rs.ProviderConfig) + ms.SetResourceInstanceCurrent(instAddr, nil, rs.ProviderConfig, addrs.NoKey) } for dk, obj := range is.Deposed { diff --git a/internal/tofu/context.go b/internal/tofu/context.go index cf034f0484..85130ca8b7 100644 --- a/internal/tofu/context.go +++ b/internal/tofu/context.go @@ -294,13 +294,15 @@ func (c *Context) watchStop(walker *ContextGraphWalker) (chan struct{}, <-chan s // Copy the providers so that a misbehaved blocking Stop doesn't // completely hang OpenTofu. walker.providerLock.Lock() - ps := make([]providers.Interface, 0, len(walker.providerCache)) - for _, p := range walker.providerCache { - ps = append(ps, p) + toStop := make([]providers.Interface, 0, len(walker.providerCache)) + for _, providerMap := range walker.providerCache { + for _, provider := range providerMap { + toStop = append(toStop, provider) + } } defer walker.providerLock.Unlock() - for _, p := range ps { + for _, p := range toStop { // We ignore the error for now since there isn't any reasonable // action to take if there is an error here, since the stop is still // advisory: OpenTofu will exit once the graph node completes. diff --git a/internal/tofu/context_apply2_test.go b/internal/tofu/context_apply2_test.go index 84d4661f54..dfc1fc1346 100644 --- a/internal/tofu/context_apply2_test.go +++ b/internal/tofu/context_apply2_test.go @@ -21,11 +21,14 @@ import ( "github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/checks" "github.com/opentofu/opentofu/internal/configs/configschema" + "github.com/opentofu/opentofu/internal/encryption" "github.com/opentofu/opentofu/internal/lang/marks" "github.com/opentofu/opentofu/internal/plans" "github.com/opentofu/opentofu/internal/providers" "github.com/opentofu/opentofu/internal/states" + "github.com/opentofu/opentofu/internal/states/statefile" "github.com/opentofu/opentofu/internal/tfdiags" + "github.com/opentofu/opentofu/version" ) // Test that the PreApply hook is called with the correct deposed key @@ -46,6 +49,7 @@ func TestContext2Apply_createBeforeDestroy_deposedKeyPreApply(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceDeposed( mustResourceInstanceAddr("aws_instance.bar").Resource, @@ -55,6 +59,7 @@ func TestContext2Apply_createBeforeDestroy_deposedKeyPreApply(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) hook := new(MockHook) @@ -229,7 +234,7 @@ resource "test_instance" "a" { s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"id":"a","value":"old","type":"test"}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) // test_instance.b depended on test_instance.a, and therefor should be // destroyed before any changes to test_instance.a @@ -237,7 +242,7 @@ resource "test_instance" "a" { AttrsJSON: []byte(`{"id":"b"}`), Status: states.ObjectReady, Dependencies: []addrs.ConfigResource{addrA.ContainingResource().Config()}, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) ctx := testContext2(t, &ContextOpts{ @@ -279,6 +284,7 @@ func TestApply_updateDependencies(t *testing.T) { }, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( binAddr.Resource, @@ -290,6 +296,7 @@ func TestApply_updateDependencies(t *testing.T) { }, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( bazAddr.Resource, @@ -302,6 +309,7 @@ func TestApply_updateDependencies(t *testing.T) { }, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( barAddr.Resource, @@ -310,6 +318,7 @@ func TestApply_updateDependencies(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","foo":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) m := testModuleInline(t, map[string]string{ @@ -428,7 +437,7 @@ resource "test_resource" "b" { }, }, Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey, ) }) @@ -587,6 +596,7 @@ resource "test_object" "x" { AttrsJSON: []byte(`{"test_string":"deposed"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -765,6 +775,7 @@ resource "test_object" "b" { AttrsJSON: []byte(`{"test_string":"ok"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.b").Resource, @@ -773,6 +784,7 @@ resource "test_object" "b" { AttrsJSON: []byte(`{"test_string":"ok"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -1475,6 +1487,7 @@ resource "test_object" "x" { AttrsJSON: []byte(`{"test_string":"ok"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -1525,6 +1538,7 @@ resource "test_object" "y" { AttrsJSON: []byte(`{"test_string":"x"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -1681,6 +1695,7 @@ output "from_resource" { AttrsJSON: []byte(`{"test_string":"wrong_val"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -1736,6 +1751,7 @@ output "from_resource" { AttrsJSON: []byte(`{"test_string":"wrong val"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) mod.SetOutputValue("from_resource", cty.StringVal("wrong val"), false) @@ -1806,6 +1822,7 @@ resource "test_object" "y" { AttrsJSON: []byte(`{"test_string":"y"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -2318,6 +2335,7 @@ func TestContext2Apply_forgetOrphanAndDeposed(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceDeposed( mustResourceInstanceAddr(addr).Resource, @@ -2328,6 +2346,7 @@ func TestContext2Apply_forgetOrphanAndDeposed(t *testing.T) { Dependencies: []addrs.ConfigResource{}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ Providers: map[addrs.Provider]providers.Factory{ @@ -2358,6 +2377,372 @@ func TestContext2Apply_forgetOrphanAndDeposed(t *testing.T) { } } +func TestContext2Apply_providerExpandWithTargetOrExclude(t *testing.T) { + // This test is covering a potentially-tricky interaction between the + // logic that updates the provider instance references for resource + // instances in state snapshots, and the -target/-exclude features which + // cause OpenTofu to skip visiting certain objects. + // + // The main priority is that we never leave the final state snapshot + // in a form that would cause errors or incorrect behavior on a future + // plan/apply round. This test covers the current way we resolve the + // ambiguity at the time of writing -- by updating the provider + // instance addresses of all instances of any resource where at least + // one instance is included in the plan -- but if future changes make + // this test fail then it might be valid to introduce a different rule + // as long as it still guarantees to create a valid final state snapshot. + + rsrcFirst := addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "mock", + Name: "first", + }.Absolute(addrs.RootModuleInstance) + rsrcFirstInstA := rsrcFirst.Instance(addrs.StringKey("a")) + rsrcFirstInstB := rsrcFirst.Instance(addrs.StringKey("b")) + rsrcSecond := addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "mock", + Name: "second", + }.Absolute(addrs.RootModuleInstance) + rsrcSecondInstA := rsrcSecond.Instance(addrs.StringKey("a")) + rsrcSecondInstB := rsrcSecond.Instance(addrs.StringKey("b")) + + // We use the same test sequence for both -target and -exclude, with + // makeStep2PlanOpts providing whichever filter is appropriate for + // each test. + // For correct operation the plan options must cause OpenTofu to + // skip both instances of mock.second and visit at least one instance + // of mock.first. + runTest := func(t *testing.T, makeStep2PlanOpts func(plans.Mode) *PlanOpts) { + mockProviderAddr := addrs.NewBuiltInProvider("mock") + providerConfigBefore := addrs.AbsProviderConfig{ + Module: addrs.RootModule, + Provider: mockProviderAddr, + Alias: "before", + } + providerConfigAfter := addrs.AbsProviderConfig{ + Module: addrs.RootModule, + Provider: mockProviderAddr, + Alias: "after", + } + normalPlanOpts := &PlanOpts{ + Mode: plans.NormalMode, + } + p := &MockProvider{ + GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ + Provider: providers.Schema{ + Block: &configschema.Block{}, + }, + ResourceTypes: map[string]providers.Schema{ + "mock": { + Block: &configschema.Block{}, + }, + }, + }, + } + + // For this particular test we'll also save state snapshots in JSON format, + // so that we're exercising the state snapshot writer and reader in similar way + // to how OpenTofu CLI would do it, in case its normalization rules and + // consistency checks cause any problems that we can't notice when we're just + // passing pointers to the live data structure. + var stateJSON []byte + readStateSnapshot := func(t *testing.T) *states.State { + t.Helper() + if len(stateJSON) == 0 { + return states.NewState() + } + ret, err := statefile.Read(bytes.NewReader(stateJSON), encryption.StateEncryptionDisabled()) + if err != nil { + t.Fatalf("failed to read latest state snapshot: %s", err) + } + return ret.State + } + writeStateSnapshot := func(t *testing.T, state *states.State) { + t.Helper() + f := &statefile.File{ + State: state, + TerraformVersion: version.SemVer, + } + buf := bytes.NewBuffer(nil) + err := statefile.Write(f, buf, encryption.StateEncryptionDisabled()) + if err != nil { + t.Fatalf("failed to write new state snapshot: %s", err) + } + stateJSON = buf.Bytes() + } + assertResourceInstanceProviderInstance := func( + t *testing.T, + state *states.State, + addr addrs.AbsResourceInstance, + wantConfigAddr addrs.AbsProviderConfig, + wantKey addrs.InstanceKey, + ) { + t.Helper() + rsrcAddr := addr.ContainingResource() + r := state.Resource(rsrcAddr) + if r == nil { + t.Fatalf("state has no record of %s", rsrcAddr) + } + ri := r.Instance(addr.Resource.Key) + if ri == nil { + t.Fatalf("state has no record of %s", addr) + } + ok := true + if got, want := r.ProviderConfig, wantConfigAddr; got.String() != want.String() { + t.Errorf( + "%s has incorrect provider configuration address\ngot: %s\nwant: %s", + rsrcAddr, got, want, + ) + ok = false + } + if got, want := ri.ProviderKey, wantKey; got != want { + t.Errorf( + "%s has incorrect provider instance key\ngot: %s\nwant: %s", + addr, got, want, + ) + ok = false + } + if !ok { + t.FailNow() + } + } + + t.Log("Step 1: Apply with a multi-instance provider config and two resources to create our initial state") + { + state := readStateSnapshot(t) + m := testModuleInline(t, map[string]string{ + "main.tf": ` + terraform { + required_providers { + mock = { + source = "terraform.io/builtin/mock" + } + } + } + + locals { + instances = toset(["a", "b"]) + } + + provider "mock" { + alias = "before" + for_each = local.instances + } + + resource "mock" "first" { + # NOTE: This is a bad example to follow in a real config, + # since it would not be possible to remove elements from + # local.instances without encountering an error. We don't + # intend to do that for this test, though. + for_each = local.instances + provider = mock.before[each.key] + } + + resource "mock" "second" { + for_each = local.instances + provider = mock.before[each.key] + } + `, + }) + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewBuiltInProvider("mock"): testProviderFuncFixed(p), + }, + }) + plan, diags := ctx.Plan(m, state, normalPlanOpts) + assertNoErrors(t, diags) + + newState, diags := ctx.Apply(plan, m) + assertNoErrors(t, diags) + + assertResourceInstanceProviderInstance( + t, newState, + rsrcFirstInstA, + providerConfigBefore, addrs.StringKey("a"), + ) + assertResourceInstanceProviderInstance( + t, newState, + rsrcFirstInstB, + providerConfigBefore, addrs.StringKey("b"), + ) + assertResourceInstanceProviderInstance( + t, newState, + rsrcSecondInstA, + providerConfigBefore, addrs.StringKey("a"), + ) + assertResourceInstanceProviderInstance( + t, newState, + rsrcSecondInstB, + providerConfigBefore, addrs.StringKey("b"), + ) + + writeStateSnapshot(t, newState) + } + + t.Log("Step 2: Change the provider configuration address in config but then apply with some graph nodes excluded") + { + state := readStateSnapshot(t) + m := testModuleInline(t, map[string]string{ + "main.tf": ` + terraform { + required_providers { + mock = { + source = "terraform.io/builtin/mock" + } + } + } + + locals { + instances = toset(["a", "b"]) + } + + provider "mock" { + alias = "after" + for_each = local.instances + } + + resource "mock" "first" { + for_each = local.instances + provider = mock.after[each.key] + } + + resource "mock" "second" { + for_each = local.instances + provider = mock.after[each.key] + } + `, + }) + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewBuiltInProvider("mock"): testProviderFuncFixed(p), + }, + }) + plan, diags := ctx.Plan(m, state, makeStep2PlanOpts(plans.NormalMode)) + assertNoErrors(t, diags) + + newState, diags := ctx.Apply(plan, m) + assertNoErrors(t, diags) + + // Because makeStep2PlanOpts told us to retain at least one + // instance of mock.first, both instances should've been + // updated to refer to the new provider instance addresses. + assertResourceInstanceProviderInstance( + t, newState, + rsrcFirstInstA, + providerConfigAfter, addrs.StringKey("a"), + ) + assertResourceInstanceProviderInstance( + t, newState, + rsrcFirstInstB, + providerConfigAfter, addrs.StringKey("b"), + ) + // Because makeStep2PlanOpts told us to exclude both instances + // of mock.second, they continue to refer to the original + // provider instance addresses. + assertResourceInstanceProviderInstance( + t, newState, + rsrcSecondInstA, + providerConfigBefore, addrs.StringKey("a"), + ) + assertResourceInstanceProviderInstance( + t, newState, + rsrcSecondInstB, + providerConfigBefore, addrs.StringKey("b"), + ) + + writeStateSnapshot(t, newState) + } + + t.Log("Step 3: Remove the mock.first resource completely to destroy both instances using the updated provider config") + { + state := readStateSnapshot(t) + m := testModuleInline(t, map[string]string{ + "main.tf": ` + terraform { + required_providers { + mock = { + source = "terraform.io/builtin/mock" + } + } + } + + locals { + instances = toset(["a", "b"]) + } + + provider "mock" { + alias = "after" + for_each = local.instances + } + + # mock.first intentionally removed, which should succeed + # because the incoming state snapshot should remember that + # it was associated with mock.after . + + resource "mock" "second" { + for_each = local.instances + provider = mock.after[each.key] + } + `, + }) + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewBuiltInProvider("mock"): testProviderFuncFixed(p), + }, + }) + plan, diags := ctx.Plan(m, state, normalPlanOpts) + assertNoErrors(t, diags) + + newState, diags := ctx.Apply(plan, m) + assertNoErrors(t, diags) + + // The whole resource state for mock.first should've been removed now. + if rs := newState.Resource(rsrcFirst); rs != nil { + t.Errorf("final state still contains %s", rsrcFirst) + } + + // We didn't target or exclude anything this time, so we + // should now have both instances of mock.second updated + // to refer to the new provider config. + assertResourceInstanceProviderInstance( + t, newState, + rsrcSecondInstA, + providerConfigAfter, addrs.StringKey("a"), + ) + assertResourceInstanceProviderInstance( + t, newState, + rsrcSecondInstB, + providerConfigAfter, addrs.StringKey("b"), + ) + + writeStateSnapshot(t, newState) + } + } + + t.Run("with target", func(t *testing.T) { + runTest(t, func(planMode plans.Mode) *PlanOpts { + return &PlanOpts{ + Mode: planMode, + Targets: []addrs.Targetable{ + rsrcFirstInstA, + }, + } + }) + }) + t.Run("with exclude", func(t *testing.T) { + runTest(t, func(planMode plans.Mode) *PlanOpts { + return &PlanOpts{ + Mode: planMode, + Excludes: []addrs.Targetable{ + rsrcSecondInstA, + rsrcSecondInstB, + }, + } + }) + }) +} + // All exclude flag tests in this file, from here forward, are inspired by some counterpart target flag test // either from this file or from context_apply_test.go func TestContext2Apply_moduleProviderAliasExcludes(t *testing.T) { @@ -3000,6 +3385,7 @@ func TestContext2Apply_excludedDestroyCountDeps(t *testing.T) { AttrsJSON: []byte(`{"id":"i-bcd345"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[1]").Resource, @@ -3008,6 +3394,7 @@ func TestContext2Apply_excludedDestroyCountDeps(t *testing.T) { AttrsJSON: []byte(`{"id":"i-cde345"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[2]").Resource, @@ -3016,6 +3403,7 @@ func TestContext2Apply_excludedDestroyCountDeps(t *testing.T) { AttrsJSON: []byte(`{"id":"i-def345"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar").Resource, @@ -3025,6 +3413,7 @@ func TestContext2Apply_excludedDestroyCountDeps(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3074,6 +3463,7 @@ func TestContext2Apply_excludedDependentDestroyCountDeps(t *testing.T) { AttrsJSON: []byte(`{"id":"i-bcd345"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[1]").Resource, @@ -3082,6 +3472,7 @@ func TestContext2Apply_excludedDependentDestroyCountDeps(t *testing.T) { AttrsJSON: []byte(`{"id":"i-cde345"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[2]").Resource, @@ -3090,6 +3481,7 @@ func TestContext2Apply_excludedDependentDestroyCountDeps(t *testing.T) { AttrsJSON: []byte(`{"id":"i-def345"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar").Resource, @@ -3099,6 +3491,7 @@ func TestContext2Apply_excludedDependentDestroyCountDeps(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3154,6 +3547,7 @@ func TestContext2Apply_excludedDestroyModule(t *testing.T) { AttrsJSON: []byte(`{"id":"i-bcd345"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar").Resource, @@ -3162,6 +3556,7 @@ func TestContext2Apply_excludedDestroyModule(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc123"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) child.SetResourceInstanceCurrent( @@ -3171,6 +3566,7 @@ func TestContext2Apply_excludedDestroyModule(t *testing.T) { AttrsJSON: []byte(`{"id":"i-bcd345"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) child.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar").Resource, @@ -3179,6 +3575,7 @@ func TestContext2Apply_excludedDestroyModule(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc123"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3230,31 +3627,37 @@ func TestContext2Apply_excludedDestroyCountIndex(t *testing.T) { mustResourceInstanceAddr("aws_instance.foo[0]").Resource, foo, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[1]").Resource, foo, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[2]").Resource, foo, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar[0]").Resource, bar, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar[1]").Resource, bar, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar[2]").Resource, bar, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3552,6 +3955,7 @@ func TestContext2Apply_excludedResourceOrphanModule(t *testing.T) { AttrsJSON: []byte(`{"id":"abc","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3604,6 +4008,7 @@ func TestContext2Apply_excludedOrphanModule(t *testing.T) { AttrsJSON: []byte(`{"id":"abc","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3654,6 +4059,7 @@ func TestContext2Apply_excludedWithTaintedInState(t *testing.T) { AttrsJSON: []byte(`{"id":"ifailedprovisioners"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ diff --git a/internal/tofu/context_apply_checks_test.go b/internal/tofu/context_apply_checks_test.go index e09ab6801b..187c6dfa6f 100644 --- a/internal/tofu/context_apply_checks_test.go +++ b/internal/tofu/context_apply_checks_test.go @@ -472,7 +472,7 @@ check "error" { addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, - }) + }, addrs.NoKey) }), plan: map[string]checksTestingStatus{ "error": { @@ -572,7 +572,7 @@ check "passing" { addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, - }) + }, addrs.NoKey) }), plan: map[string]checksTestingStatus{ "passing": { diff --git a/internal/tofu/context_apply_test.go b/internal/tofu/context_apply_test.go index 094894b66d..10cce41e78 100644 --- a/internal/tofu/context_apply_test.go +++ b/internal/tofu/context_apply_test.go @@ -462,6 +462,7 @@ func TestContext2Apply_resourceDependsOnModuleStateOnly(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.aws_instance.child")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) child.SetResourceInstanceCurrent( @@ -471,6 +472,7 @@ func TestContext2Apply_resourceDependsOnModuleStateOnly(t *testing.T) { AttrsJSON: []byte(`{"id":"child"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) { @@ -944,6 +946,7 @@ func TestContext2Apply_createBeforeDestroy(t *testing.T) { AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ Providers: map[addrs.Provider]providers.Factory{ @@ -1013,6 +1016,7 @@ func TestContext2Apply_createBeforeDestroyUpdate(t *testing.T) { CreateBeforeDestroy: true, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar").Resource, @@ -1023,6 +1027,7 @@ func TestContext2Apply_createBeforeDestroyUpdate(t *testing.T) { Dependencies: []addrs.ConfigResource{fooAddr.ContainingResource().Config()}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -1072,6 +1077,7 @@ func TestContext2Apply_createBeforeDestroy_dependsNonCBD(t *testing.T) { AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo").Resource, @@ -1080,6 +1086,7 @@ func TestContext2Apply_createBeforeDestroy_dependsNonCBD(t *testing.T) { AttrsJSON: []byte(`{"id":"foo", "require_new": "abc"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -1133,6 +1140,7 @@ func TestContext2Apply_createBeforeDestroy_hook(t *testing.T) { AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) var actual []cty.Value @@ -1194,6 +1202,7 @@ func TestContext2Apply_createBeforeDestroy_deposedCount(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceDeposed( mustResourceInstanceAddr("aws_instance.bar[0]").Resource, @@ -1203,6 +1212,7 @@ func TestContext2Apply_createBeforeDestroy_deposedCount(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar[1]").Resource, @@ -1211,6 +1221,7 @@ func TestContext2Apply_createBeforeDestroy_deposedCount(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceDeposed( mustResourceInstanceAddr("aws_instance.bar[1]").Resource, @@ -1220,6 +1231,7 @@ func TestContext2Apply_createBeforeDestroy_deposedCount(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -1271,6 +1283,7 @@ func TestContext2Apply_createBeforeDestroy_deposedOnly(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceDeposed( mustResourceInstanceAddr("aws_instance.bar").Resource, @@ -1280,6 +1293,7 @@ func TestContext2Apply_createBeforeDestroy_deposedOnly(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -1321,6 +1335,7 @@ func TestContext2Apply_destroyComputed(t *testing.T) { AttrsJSON: []byte(`{"id":"foo", "output": "value"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ Providers: map[addrs.Provider]providers.Factory{ @@ -1367,6 +1382,7 @@ func testContext2Apply_destroyDependsOn(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo").Resource, @@ -1376,6 +1392,7 @@ func testContext2Apply_destroyDependsOn(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.bar")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) // Record the order we see Apply @@ -1432,6 +1449,7 @@ func TestContext2Apply_destroyDependsOnStateOnly(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) root.SetResourceInstanceCurrent( addrs.Resource{ @@ -1457,6 +1475,7 @@ func TestContext2Apply_destroyDependsOnStateOnly(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) // It is possible for this to be racy, so we loop a number of times @@ -1526,6 +1545,7 @@ func TestContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) child.SetResourceInstanceCurrent( addrs.Resource{ @@ -1551,6 +1571,7 @@ func TestContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) // It is possible for this to be racy, so we loop a number of times @@ -1663,6 +1684,7 @@ func TestContext2Apply_destroyData(t *testing.T) { AttrsJSON: []byte(`{"id":"-"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/null"]`), + addrs.NoKey, ) hook := &testHook{} @@ -1722,6 +1744,7 @@ func TestContext2Apply_destroySkipsCBD(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar").Resource, @@ -1730,6 +1753,7 @@ func TestContext2Apply_destroySkipsCBD(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -1768,6 +1792,7 @@ func TestContext2Apply_destroyModuleVarProviderConfig(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ Providers: map[addrs.Provider]providers.Factory{ @@ -1844,6 +1869,7 @@ func getContextForApply_destroyCrossProviders(t *testing.T, m *configs.Config, p AttrsJSON: []byte(`{"id":"test"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) child.SetResourceInstanceCurrent( @@ -1853,6 +1879,7 @@ func getContextForApply_destroyCrossProviders(t *testing.T, m *configs.Config, p AttrsJSON: []byte(`{"id": "vpc-aaabbb12", "value":"test"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -2199,6 +2226,7 @@ func TestContext2Apply_countDecrease(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","foo": "foo","type": "aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[1]").Resource, @@ -2207,6 +2235,7 @@ func TestContext2Apply_countDecrease(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","foo": "foo","type": "aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[2]").Resource, @@ -2215,6 +2244,7 @@ func TestContext2Apply_countDecrease(t *testing.T) { AttrsJSON: []byte(`{"id":"bar", "foo": "foo", "type": "aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -2249,6 +2279,7 @@ func TestContext2Apply_countDecreaseToOneX(t *testing.T) { AttrsJSON: []byte(`{"id":"bar", "foo": "foo", "type": "aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[1]").Resource, @@ -2257,6 +2288,7 @@ func TestContext2Apply_countDecreaseToOneX(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[2]").Resource, @@ -2265,6 +2297,7 @@ func TestContext2Apply_countDecreaseToOneX(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -2319,6 +2352,7 @@ func TestContext2Apply_countDecreaseToOneCorrupted(t *testing.T) { AttrsJSON: []byte(`{"id":"bar", "foo": "foo", "type": "aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[0]").Resource, @@ -2327,6 +2361,7 @@ func TestContext2Apply_countDecreaseToOneCorrupted(t *testing.T) { AttrsJSON: []byte(`{"id":"baz", "type": "aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -2395,6 +2430,7 @@ func TestContext2Apply_countTainted(t *testing.T) { AttrsJSON: []byte(`{"id":"bar", "type": "aws_instance", "foo": "foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ Providers: map[addrs.Provider]providers.Factory{ @@ -2652,6 +2688,7 @@ func TestContext2Apply_moduleDestroyOrder(t *testing.T) { AttrsJSON: []byte(`{"id":"a"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root := state.EnsureModule(addrs.RootModuleInstance) root.SetResourceInstanceCurrent( @@ -2662,6 +2699,7 @@ func TestContext2Apply_moduleDestroyOrder(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.aws_instance.a")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -2787,7 +2825,7 @@ func TestContext2Apply_orphanResource(t *testing.T) { s.SetResourceInstanceCurrent(oneAddr.Instance(addrs.IntKey(0)), &states.ResourceInstanceObjectSrc{ Status: states.ObjectReady, AttrsJSON: []byte(`{"id":"foo"}`), - }, providerAddr) + }, providerAddr, addrs.NoKey) }) if state.String() != want.String() { @@ -2859,6 +2897,7 @@ func TestContext2Apply_moduleOrphanInheritAlias(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -2922,6 +2961,7 @@ func TestContext2Apply_moduleOrphanProvider(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -2962,6 +3002,7 @@ func TestContext2Apply_moduleOrphanGrandchildProvider(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3131,6 +3172,7 @@ func TestContext2Apply_moduleProviderCloseNested(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3168,6 +3210,7 @@ func TestContext2Apply_moduleVarRefExisting(t *testing.T) { AttrsJSON: []byte(`{"id":"foo","foo":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -4453,6 +4496,7 @@ func TestContext2Apply_provisionerFail_createBeforeDestroy(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","require_new":"abc"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -4492,6 +4536,7 @@ func TestContext2Apply_error_createBeforeDestroy(t *testing.T) { AttrsJSON: []byte(`{"id":"bar", "require_new": "abc","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -4538,6 +4583,7 @@ func TestContext2Apply_errorDestroy_createBeforeDestroy(t *testing.T) { AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -4596,6 +4642,7 @@ func TestContext2Apply_multiDepose_createBeforeDestroy(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) p.PlanResourceChangeFn = testDiffFn @@ -4885,6 +4932,7 @@ func TestContext2Apply_provisionerDestroy(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","foo":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -4932,6 +4980,7 @@ func TestContext2Apply_provisionerDestroyFail(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","foo":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -4996,6 +5045,7 @@ func TestContext2Apply_provisionerDestroyFailContinue(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -5063,6 +5113,7 @@ func TestContext2Apply_provisionerDestroyFailContinueFail(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -5129,6 +5180,7 @@ func TestContext2Apply_provisionerDestroyTainted(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -5575,6 +5627,7 @@ func TestContext2Apply_outputDiffVars(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -5796,6 +5849,7 @@ func TestContext2Apply_destroyNestedModule(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -5834,6 +5888,7 @@ func TestContext2Apply_destroyDeeplyNestedModule(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -6233,6 +6288,7 @@ func TestContext2Apply_destroyOrphan(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ Providers: map[addrs.Provider]providers.Factory{ @@ -6271,6 +6327,7 @@ func TestContext2Apply_destroyTaintedProvisioner(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -6391,6 +6448,7 @@ func TestContext2Apply_errorDestroy(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) plan, diags := ctx.Plan(m, state, DefaultPlanOpts) @@ -6523,6 +6581,7 @@ func TestContext2Apply_errorUpdateNullNew(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) plan, diags := ctx.Plan(m, state, DefaultPlanOpts) @@ -6573,6 +6632,7 @@ func TestContext2Apply_errorPartial(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -6657,6 +6717,7 @@ func TestContext2Apply_hookOrphan(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -6896,6 +6957,7 @@ func TestContext2Apply_taintX(t *testing.T) { AttrsJSON: []byte(`{"id":"baz","num": "2", "type": "aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -6942,6 +7004,7 @@ func TestContext2Apply_taintDep(t *testing.T) { AttrsJSON: []byte(`{"id":"baz","num": "2", "type": "aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar").Resource, @@ -6951,6 +7014,7 @@ func TestContext2Apply_taintDep(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -6993,6 +7057,7 @@ func TestContext2Apply_taintDepRequiresNew(t *testing.T) { AttrsJSON: []byte(`{"id":"baz","num": "2", "type": "aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar").Resource, @@ -7002,6 +7067,7 @@ func TestContext2Apply_taintDepRequiresNew(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -7243,6 +7309,7 @@ func TestContext2Apply_targetedDestroyCountDeps(t *testing.T) { AttrsJSON: []byte(`{"id":"i-bcd345"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar").Resource, @@ -7252,6 +7319,7 @@ func TestContext2Apply_targetedDestroyCountDeps(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -7293,6 +7361,7 @@ func TestContext2Apply_targetedDestroyModule(t *testing.T) { AttrsJSON: []byte(`{"id":"i-bcd345"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar").Resource, @@ -7301,6 +7370,7 @@ func TestContext2Apply_targetedDestroyModule(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc123"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) child.SetResourceInstanceCurrent( @@ -7310,6 +7380,7 @@ func TestContext2Apply_targetedDestroyModule(t *testing.T) { AttrsJSON: []byte(`{"id":"i-bcd345"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) child.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar").Resource, @@ -7318,6 +7389,7 @@ func TestContext2Apply_targetedDestroyModule(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc123"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -7376,31 +7448,37 @@ func TestContext2Apply_targetedDestroyCountIndex(t *testing.T) { mustResourceInstanceAddr("aws_instance.foo[0]").Resource, foo, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[1]").Resource, foo, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[2]").Resource, foo, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar[0]").Resource, bar, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar[1]").Resource, bar, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar[2]").Resource, bar, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -7654,6 +7732,7 @@ func TestContext2Apply_targetedResourceOrphanModule(t *testing.T) { AttrsJSON: []byte(`{"id":"abc","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -7889,6 +7968,7 @@ func TestContext2Apply_createBefore_depends(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) root.SetResourceInstanceCurrent( @@ -7915,6 +7995,7 @@ func TestContext2Apply_createBefore_depends(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -8017,6 +8098,7 @@ func TestContext2Apply_singleDestroy(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) root.SetResourceInstanceCurrent( @@ -8043,6 +8125,7 @@ func TestContext2Apply_singleDestroy(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -8227,6 +8310,7 @@ func TestContext2Apply_targetedWithTaintedInState(t *testing.T) { AttrsJSON: []byte(`{"id":"ifailedprovisioners"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -8360,6 +8444,7 @@ func TestContext2Apply_ignoreChangesWithDep(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc123","ami":"ami-abcd1234"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[1]").Resource, @@ -8368,6 +8453,7 @@ func TestContext2Apply_ignoreChangesWithDep(t *testing.T) { AttrsJSON: []byte(`{"id":"i-bcd234","ami":"i-bcd234"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_eip.foo[0]").Resource, @@ -8386,6 +8472,7 @@ func TestContext2Apply_ignoreChangesWithDep(t *testing.T) { }, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_eip.foo[1]").Resource, @@ -8404,6 +8491,7 @@ func TestContext2Apply_ignoreChangesWithDep(t *testing.T) { }, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -8830,6 +8918,7 @@ func TestContext2Apply_destroyWithLocals(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetOutputValue("name", cty.StringVal("test-bar"), false) @@ -8925,6 +9014,7 @@ func TestContext2Apply_destroyWithProviders(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"].baz`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -8979,6 +9069,7 @@ func TestContext2Apply_providersFromState(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) aliasedProviderState := states.NewState() @@ -8990,6 +9081,7 @@ func TestContext2Apply_providersFromState(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"].bar`), + addrs.NoKey, ) moduleProviderState := states.NewState() @@ -9001,6 +9093,7 @@ func TestContext2Apply_providersFromState(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`module.child.provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) for _, tc := range []struct { @@ -9080,6 +9173,7 @@ func TestContext2Apply_plannedInterpolatedCount(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -9130,6 +9224,7 @@ func TestContext2Apply_plannedDestroyInterpolatedCount(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.a[1]").Resource, @@ -9138,6 +9233,7 @@ func TestContext2Apply_plannedDestroyInterpolatedCount(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetOutputValue("out", cty.ListVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("foo")}), false) @@ -9193,6 +9289,7 @@ func TestContext2Apply_scaleInMultivarRef(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.two").Resource, @@ -9201,6 +9298,7 @@ func TestContext2Apply_scaleInMultivarRef(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -9356,6 +9454,7 @@ func TestContext2Apply_issue19908(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) @@ -9473,6 +9572,7 @@ func TestContext2Apply_moduleReplaceCycle(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) modB := state.EnsureModule(addrs.RootModuleInstance.Child("b", addrs.NoKey)) @@ -9490,6 +9590,7 @@ func TestContext2Apply_moduleReplaceCycle(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) aBefore, _ := plans.NewDynamicValue( @@ -9612,6 +9713,7 @@ func TestContext2Apply_destroyDataCycle(t *testing.T) { Provider: addrs.NewDefaultProvider("null"), Module: addrs.RootModule, }, + addrs.NoKey, ) root.SetResourceInstanceCurrent( addrs.Resource{ @@ -9637,6 +9739,7 @@ func TestContext2Apply_destroyDataCycle(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) root.SetResourceInstanceCurrent( addrs.Resource{ @@ -9652,6 +9755,7 @@ func TestContext2Apply_destroyDataCycle(t *testing.T) { Provider: addrs.NewDefaultProvider("null"), Module: addrs.RootModule, }, + addrs.NoKey, ) Providers := map[addrs.Provider]providers.Factory{ @@ -9759,6 +9863,7 @@ func TestContext2Apply_taintedDestroyFailure(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) root.SetResourceInstanceCurrent( addrs.Resource{ @@ -9774,6 +9879,7 @@ func TestContext2Apply_taintedDestroyFailure(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) root.SetResourceInstanceCurrent( addrs.Resource{ @@ -9789,6 +9895,7 @@ func TestContext2Apply_taintedDestroyFailure(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) Providers := map[addrs.Provider]providers.Factory{ @@ -9964,6 +10071,7 @@ func TestContext2Apply_cbdCycle(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) root.SetResourceInstanceCurrent( addrs.Resource{ @@ -9989,6 +10097,7 @@ func TestContext2Apply_cbdCycle(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) root.SetResourceInstanceCurrent( addrs.Resource{ @@ -10004,6 +10113,7 @@ func TestContext2Apply_cbdCycle(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) Providers := map[addrs.Provider]providers.Factory{ @@ -11237,6 +11347,7 @@ locals { CreateBeforeDestroy: true, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_instance.a[1]").Resource, @@ -11247,6 +11358,7 @@ locals { CreateBeforeDestroy: true, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_instance.b").Resource, @@ -11257,6 +11369,7 @@ locals { CreateBeforeDestroy: true, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_instance.c").Resource, @@ -11270,6 +11383,7 @@ locals { CreateBeforeDestroy: true, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) p := testProvider("test") @@ -12181,6 +12295,7 @@ resource "test_resource" "foo" { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) @@ -12724,7 +12839,7 @@ func TestContext2Apply_errorRestorePrivateData(t *testing.T) { Status: states.ObjectReady, AttrsJSON: []byte(`{"id":"foo"}`), Private: []byte("private"), - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) ctx := testContext2(t, &ContextOpts{ @@ -12769,7 +12884,7 @@ func TestContext2Apply_errorRestoreStatus(t *testing.T) { AttrsJSON: []byte(`{"test_string":"foo"}`), Private: []byte("private"), Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.b")}, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) ctx := testContext2(t, &ContextOpts{ diff --git a/internal/tofu/context_functions_test.go b/internal/tofu/context_functions_test.go index b2d534b403..475a0f6d38 100644 --- a/internal/tofu/context_functions_test.go +++ b/internal/tofu/context_functions_test.go @@ -635,18 +635,126 @@ variable "obfmod" { }, }) - diags := ctx.Validate(m) + _, diags := ctx.Plan(m, nil, nil) if !diags.HasErrors() { t.Fatal("Expected error!") } - expected := `Unknown provider function: Provider "module.mod.provider[\"registry.opentofu.org/hashicorp/aws\"]" does not have a function "arn_parse_custom" or has not been configured` + expected := `Function not found in provider: Function "provider::aws::arn_parse_custom" was not registered by provider` if expected != diags.Err().Error() { t.Fatalf("Expected error %q, got %q", expected, diags.Err().Error()) } - if p.GetFunctionsCalled { - t.Fatalf("Unexpected function call") + if !p.GetFunctionsCalled { + t.Fatalf("Expected function call") } if p.CallFunctionCalled { t.Fatalf("Unexpected function call") } } + +// Defaulted stub provider +func TestContext2Functions_providerFunctionsForEachCount(t *testing.T) { + p := testProvider("aws") + addr := addrs.ImpliedProviderForUnqualifiedType("aws") + + // Explicitly non-parallel + t.Setenv("foo", "bar") + defer providers.SchemaCache.Remove(addr) + + p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ + Functions: map[string]providers.FunctionSpec{ + "arn_parse": providers.FunctionSpec{ + Parameters: []providers.FunctionParameterSpec{{ + Name: "arn", + Type: cty.String, + }}, + Return: cty.Bool, + }, + }, + } + p.CallFunctionResponse = &providers.CallFunctionResponse{ + Result: cty.True, + } + + // SchemaCache is initialzed earlier on in the command package + providers.SchemaCache.Set(addr, *p.GetProviderSchemaResponse) + + m := testModuleInline(t, map[string]string{ + "main.tf": ` +provider "aws" { + for_each = {"a": 1, "b": 2} + alias = "iter" +} +module "mod" { + source = "./mod" + for_each = {"a": 1, "b": 2} + providers = { + aws = aws.iter[each.key] + } +} + `, + "mod/mod.tf": ` +terraform { + required_providers { + aws = ">=5.70.0" + } +} + +variable "obfmod" { + type = object({ + arns = optional(list(string)) + }) + description = "Configuration for xxx." + + validation { + condition = alltrue([ + for arn in var.obfmod.arns: can(provider::aws::arn_parse(arn)) + ]) + error_message = "All arns MUST BE a valid AWS ARN format." + } + + default = { + arns = [ + "arn:partition:service:region:account-id:resource-id", + ] + } +} +`, + }) + + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), + }, + }) + + diags := ctx.Validate(m) + if diags.HasErrors() { + t.Fatal(diags.Err()) + } + if !p.GetProviderSchemaCalled { + t.Fatalf("Unexpected function call") + } + if p.GetFunctionsCalled { + t.Fatalf("Unexpected function call") + } + if !p.CallFunctionCalled { + t.Fatalf("Unexpected function call") + } + + p.GetProviderSchemaCalled = false + p.GetFunctionsCalled = false + p.CallFunctionCalled = false + _, diags = ctx.Plan(m, nil, nil) + if diags.HasErrors() { + t.Fatal(diags.Err()) + } + if !p.GetProviderSchemaCalled { + t.Fatalf("Unexpected function call") + } + if p.GetFunctionsCalled { + t.Fatalf("Expected function call") + } + if !p.CallFunctionCalled { + t.Fatalf("Expected function call") + } +} diff --git a/internal/tofu/context_import_test.go b/internal/tofu/context_import_test.go index c67683e5ca..1f696f62b9 100644 --- a/internal/tofu/context_import_test.go +++ b/internal/tofu/context_import_test.go @@ -7,16 +7,20 @@ package tofu import ( "errors" + "fmt" + "log" "strings" "testing" "github.com/google/go-cmp/cmp" + "github.com/zclconf/go-cty-debug/ctydebug" "github.com/zclconf/go-cty/cty" "github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/configs/configschema" "github.com/opentofu/opentofu/internal/providers" "github.com/opentofu/opentofu/internal/states" + "github.com/opentofu/opentofu/internal/tfdiags" ) func TestContextImport_basic(t *testing.T) { @@ -115,6 +119,187 @@ resource "aws_instance" "foo" { } } +func TestContextImport_multiInstanceProviderConfig(t *testing.T) { + // This test deals with the situation of importing into a resource instance + // whose resource has a dynamic instance key in its "provider" argument, + // and thus the import step needs to perform dynamic provider instance + // selection to determine exactly which provider instance to use. + + m := testModuleInline(t, map[string]string{ + "main.tf": ` + terraform { + required_providers { + test = { + source = "terraform.io/builtin/test" + } + } + } + + provider "test" { + alias = "multi" + for_each = { + a = {} + b = {} + } + + marker = each.key + } + + resource "test_thing" "test" { + for_each = { "foo" = "a" } + provider = test.multi[each.value] + } + `}) + + resourceTypeSchema := providers.Schema{ + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, + "import_marker": { + Type: cty.String, + Computed: true, + }, + "refresh_marker": { + Type: cty.String, + Computed: true, + }, + }, + }, + } + providerSchema := &providers.GetProviderSchemaResponse{ + Provider: providers.Schema{ + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "marker": { + Type: cty.String, + Required: true, + }, + }, + }, + }, + ResourceTypes: map[string]providers.Schema{ + "test_thing": resourceTypeSchema, + }, + } + + // Unlike most context tests, this one uses a real factory function so that + // we can instantiate multiple instances and distinguish them. + providerFactory := func() (providers.Interface, error) { + // The following uses log.Printf instead of t.Logf so that the logs can interleave with the + // verbose trace logs produced by the main logic in this package, to make the order of operations clearer. + // To run just this test with trace logs: + // TF_LOG=trace go test ./internal/tofu -run '^TestContextImport_multiInstanceProviderConfig$' + + ret := &MockProvider{} + var configuredMarker cty.Value + log.Printf("[TRACE] TestContextImport_multiInstanceProviderConfig: creating new instance of provider 'test' at %p", ret) + + ret.GetProviderSchemaResponse = providerSchema + ret.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) providers.ConfigureProviderResponse { + configuredMarker = req.Config.GetAttr("marker") + log.Printf("[TRACE] TestContextImport_multiInstanceProviderConfig: ConfigureProvider for %p with marker = %#v", ret, configuredMarker) + return providers.ConfigureProviderResponse{} + } + ret.ImportResourceStateFn = func(req providers.ImportResourceStateRequest) providers.ImportResourceStateResponse { + log.Printf("[TRACE] TestContextImport_multiInstanceProviderConfig: ImportResourceState for %p with marker = %#v", ret, configuredMarker) + if configuredMarker == cty.NilVal { + return providers.ImportResourceStateResponse{ + Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("ImportResourceState before ConfigureProvider")), + } + } + return providers.ImportResourceStateResponse{ + ImportedResources: []providers.ImportedResource{ + { + TypeName: "test_thing", + State: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal(req.ID), + "import_marker": configuredMarker, + "refresh_marker": cty.NullVal(cty.String), // we'll populate this in ReadResource + }), + }, + }, + } + } + ret.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { + log.Printf("[TRACE] TestContextImport_multiInstanceProviderConfig: ReadResource for %p with marker = %#v", ret, configuredMarker) + if configuredMarker == cty.NilVal { + return providers.ReadResourceResponse{ + Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("ReadResource before ConfigureProvider")), + } + } + return providers.ReadResourceResponse{ + NewState: cty.ObjectVal(map[string]cty.Value{ + "id": req.PriorState.GetAttr("id"), + "import_marker": req.PriorState.GetAttr("import_marker"), + "refresh_marker": configuredMarker, + }), + } + } + return ret, nil + } + + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewBuiltInProvider("test"): providerFactory, + }, + }) + + existingInstanceKey := addrs.StringKey("foo") + existingInstanceAddr := addrs.RootModuleInstance.ResourceInstance( + addrs.ManagedResourceMode, "test_thing", "test", existingInstanceKey, + ) + t.Logf("importing into %s, which should succeed because it's configured", existingInstanceAddr) + log.Printf("[TRACE] TestContextImport_multiInstanceProviderConfig: importing into %s, which should succeed because it's configured", existingInstanceAddr) + state, diags := ctx.Import(m, states.NewState(), &ImportOpts{ + Targets: []*ImportTarget{ + { + CommandLineImportTarget: &CommandLineImportTarget{ + Addr: existingInstanceAddr, + ID: "fake-import-id", + }, + }, + }, + }) + assertNoErrors(t, diags) + + resourceState := state.Resource(existingInstanceAddr.ContainingResource()) + + if got, want := len(resourceState.Instances), 1; got != want { + t.Errorf("unexpected number of instances %d; want %d", got, want) + } + + instanceState := resourceState.Instances[existingInstanceKey] + if instanceState == nil { + t.Fatal("no instance with key \"foo\" in final state") + } + if got, want := instanceState.ProviderKey, addrs.StringKey("a"); got != want { + t.Errorf("wrong provider key %s; want %s", got, want) + } + if instanceState.Current == nil { + t.Fatal("final resource instance has no current object") + } + + gotObjState, err := instanceState.Current.Decode(resourceTypeSchema.Block.ImpliedType()) + if err != nil { + t.Fatalf("failed to decode final resource instance object state: %s", err) + } + wantObjState := &states.ResourceInstanceObject{ + Value: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("fake-import-id"), + "import_marker": cty.StringVal("a"), + "refresh_marker": cty.StringVal("a"), + }), + Status: states.ObjectReady, + Dependencies: []addrs.ConfigResource{}, + } + if diff := cmp.Diff(wantObjState, gotObjState, ctydebug.CmpOptions); diff != "" { + t.Error("wrong final object state\n" + diff) + } +} + func TestContextImport_importResourceWithSensitiveDataSource(t *testing.T) { p := testProvider("aws") m := testModuleInline(t, map[string]string{ @@ -218,6 +403,7 @@ func TestContextImport_collision(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) diff --git a/internal/tofu/context_input_test.go b/internal/tofu/context_input_test.go index ffbc9ab4ae..2e904754b6 100644 --- a/internal/tofu/context_input_test.go +++ b/internal/tofu/context_input_test.go @@ -446,6 +446,7 @@ func TestContext2Input_dataSourceRequiresRefresh(t *testing.T) { Provider: addrs.NewDefaultProvider("null"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) diff --git a/internal/tofu/context_plan.go b/internal/tofu/context_plan.go index 53801f6f46..94b92e1a08 100644 --- a/internal/tofu/context_plan.go +++ b/internal/tofu/context_plan.go @@ -860,16 +860,17 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State, return graph, walkPlan, diags case plans.RefreshOnlyMode: graph, diags := (&PlanGraphBuilder{ - Config: config, - State: prevRunState, - RootVariableValues: opts.SetVariables, - Plugins: c.plugins, - Targets: opts.Targets, - Excludes: opts.Excludes, - skipRefresh: opts.SkipRefresh, - skipPlanChanges: true, // this activates "refresh only" mode. - Operation: walkPlan, - ExternalReferences: opts.ExternalReferences, + Config: config, + State: prevRunState, + RootVariableValues: opts.SetVariables, + Plugins: c.plugins, + Targets: opts.Targets, + Excludes: opts.Excludes, + skipRefresh: opts.SkipRefresh, + skipPlanChanges: true, // this activates "refresh only" mode. + Operation: walkPlan, + ExternalReferences: opts.ExternalReferences, + ProviderFunctionTracker: providerFunctionTracker, }).Build(addrs.RootModuleInstance) return graph, walkPlan, diags case plans.DestroyMode: diff --git a/internal/tofu/context_plan2_test.go b/internal/tofu/context_plan2_test.go index 39818fbd03..b93b3efa83 100644 --- a/internal/tofu/context_plan2_test.go +++ b/internal/tofu/context_plan2_test.go @@ -80,7 +80,7 @@ resource "test_object" "a" { s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"arg":"previous_run"}`), Status: states.ObjectTainted, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) ctx := testContext2(t, &ContextOpts{ @@ -209,6 +209,7 @@ data "test_data_source" "foo" { }, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -251,7 +252,7 @@ output "out" { s.SetResourceInstanceCurrent(mustResourceInstanceAddr(`data.test_object.a["old"]`), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"test_string":"foo"}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) ctx := testContext2(t, &ContextOpts{ @@ -375,6 +376,7 @@ resource "test_resource" "b" { AttrsJSON: []byte(`{"id":"a"}`), Status: states.ObjectReady, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) s.SetResourceInstanceCurrent( mustResourceInstanceAddr(`module.mod["old"].test_resource.b`), @@ -382,6 +384,7 @@ resource "test_resource" "b" { AttrsJSON: []byte(`{"id":"b","value":"d"}`), Status: states.ObjectReady, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) s.SetResourceInstanceCurrent( oldDataAddr, @@ -389,6 +392,7 @@ resource "test_resource" "b" { AttrsJSON: []byte(`{"id":"d"}`), Status: states.ObjectReady, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) }) @@ -674,6 +678,7 @@ data "test_data_source" "a" { AttrsJSON: []byte(`{"id":"boop","valid":false}`), Status: states.ObjectReady, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) }) @@ -886,6 +891,7 @@ resource "test_resource" "b" { AttrsJSON: []byte(`{"id":"main","valid":false}`), Status: states.ObjectReady, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) s.SetResourceInstanceCurrent( managedAddrB, @@ -893,6 +899,7 @@ resource "test_resource" "b" { AttrsJSON: []byte(`{"id":"checker","valid":true}`), Status: states.ObjectReady, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) }) @@ -986,7 +993,7 @@ resource "test_object" "a" { s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"arg":"before"}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) ctx := testContext2(t, &ContextOpts{ @@ -1086,7 +1093,7 @@ resource "test_object" "a" { s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"arg":"before"}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) ctx := testContext2(t, &ContextOpts{ @@ -1226,7 +1233,7 @@ provider "test" { s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"test_string":"foo"}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) ctx := testContext2(t, &ContextOpts{ @@ -1262,7 +1269,7 @@ func TestContext2Plan_movedResourceBasic(t *testing.T) { s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -1326,11 +1333,11 @@ func TestContext2Plan_movedResourceCollision(t *testing.T) { s.SetResourceInstanceCurrent(addrNoKey, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) s.SetResourceInstanceCurrent(addrZeroKey, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -1432,11 +1439,11 @@ func TestContext2Plan_movedResourceCollisionDestroy(t *testing.T) { s.SetResourceInstanceCurrent(addrNoKey, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) s.SetResourceInstanceCurrent(addrZeroKey, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -1544,7 +1551,7 @@ func TestContext2Plan_movedResourceUntargeted(t *testing.T) { s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -1890,12 +1897,12 @@ resource "test_object" "b" { s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) s.SetResourceInstanceCurrent(addrB, &states.ResourceInstanceObjectSrc{ // old_list is no longer in the schema AttrsJSON: []byte(`{"old_list":["used to be","a list here"]}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -1953,12 +1960,12 @@ resource "test_object" "b" { s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) s.SetResourceInstanceCurrent(addrB, &states.ResourceInstanceObjectSrc{ // old_list is no longer in the schema AttrsJSON: []byte(`{"old_list":["used to be","a list here"]}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -2021,7 +2028,7 @@ func TestContext2Plan_movedResourceRefreshOnly(t *testing.T) { s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -2093,7 +2100,7 @@ func TestContext2Plan_refreshOnlyMode(t *testing.T) { s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"arg":"before"}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -2229,7 +2236,7 @@ func TestContext2Plan_refreshOnlyMode_deposed(t *testing.T) { s.SetResourceInstanceDeposed(addr, deposedKey, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"arg":"before"}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -2366,11 +2373,11 @@ func TestContext2Plan_refreshOnlyMode_orphan(t *testing.T) { s.SetResourceInstanceCurrent(addr.Instance(addrs.IntKey(0)), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"arg":"before"}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) s.SetResourceInstanceCurrent(addr.Instance(addrs.IntKey(1)), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"arg":"before"}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -2583,6 +2590,7 @@ data "test_data_source" "foo" { }, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_instance.bar").Resource, @@ -2597,6 +2605,7 @@ data "test_data_source" "foo" { }, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -2640,11 +2649,11 @@ func TestContext2Plan_forceReplace(t *testing.T) { s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) s.SetResourceInstanceCurrent(addrB, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -2708,11 +2717,11 @@ func TestContext2Plan_forceReplaceIncompleteAddr(t *testing.T) { s.SetResourceInstanceCurrent(addr0, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) s.SetResourceInstanceCurrent(addr1, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -2828,6 +2837,7 @@ output "output" { AttrsJSON: []byte(`{"id":"foo","value":"a"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) one.SetResourceInstanceCurrent( mustResourceInstanceAddr(`data.test_data_source.d`).Resource, @@ -2836,6 +2846,7 @@ output "output" { AttrsJSON: []byte(`{"id":"data"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) two.SetResourceInstanceCurrent( mustResourceInstanceAddr(`test_resource.x`).Resource, @@ -2844,6 +2855,7 @@ output "output" { AttrsJSON: []byte(`{"id":"foo","value":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) two.SetResourceInstanceCurrent( mustResourceInstanceAddr(`data.test_data_source.d`).Resource, @@ -2852,6 +2864,7 @@ output "output" { AttrsJSON: []byte(`{"id":"data"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -2919,7 +2932,7 @@ func TestContext2Plan_moduleExpandOrphansResourceInstance(t *testing.T) { s.SetResourceInstanceCurrent(addrNoKey, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -3094,7 +3107,7 @@ resource "test_resource" "a" { s.SetResourceInstanceCurrent(mustResourceInstanceAddr("test_resource.a"), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"value":"boop","output":"blorp"}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) _, diags := ctx.Plan(m, state, &PlanOpts{ Mode: plans.RefreshOnlyMode, @@ -3163,7 +3176,7 @@ resource "test_resource" "a" { s.SetResourceInstanceCurrent(mustResourceInstanceAddr("test_resource.a"), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"value":"boop","output":"blorp"}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) { newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) { @@ -3216,7 +3229,7 @@ resource "test_resource" "a" { s.SetResourceInstanceCurrent(mustResourceInstanceAddr("test_resource.a"), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"value":"boop","output":"blorp"}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) { newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) { @@ -3888,6 +3901,7 @@ resource "test_object" "b" { Status: states.ObjectReady, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) s.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.b[0]"), @@ -3896,6 +3910,7 @@ resource "test_object" "b" { Status: states.ObjectReady, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) }) for _, configuration := range configurations { @@ -3977,7 +3992,7 @@ data "test_object" "a" { s.SetResourceInstanceCurrent(mustResourceInstanceAddr(`data.test_object.a`), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"id":"old","obj":[{"args":["string"]}]}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) ctx := testContext2(t, &ContextOpts{ @@ -4018,6 +4033,7 @@ resource "test_object" "b" { Dependencies: []addrs.ConfigResource{mustResourceInstanceAddr("test_object.b").ContainingResource().Config()}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.b").Resource, @@ -4026,6 +4042,7 @@ resource "test_object" "b" { AttrsJSON: []byte(`{"test_string":"b"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -4139,6 +4156,7 @@ resource "test_object" "b" { Dependencies: []addrs.ConfigResource{}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.a[0]").Resource, @@ -4148,6 +4166,7 @@ resource "test_object" "b" { Dependencies: []addrs.ConfigResource{}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -4318,6 +4337,7 @@ output "out" { Dependencies: []addrs.ConfigResource{}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) mod.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.a[1]").Resource, @@ -4327,6 +4347,7 @@ output "out" { Dependencies: []addrs.ConfigResource{}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -4461,6 +4482,7 @@ resource "test_object" "a" { Dependencies: []addrs.ConfigResource{}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ Providers: map[addrs.Provider]providers.Factory{ @@ -5575,6 +5597,7 @@ import { AttrsJSON: []byte(`{"test_string":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) plan, diags := ctx.Plan(m, state, DefaultPlanOpts) @@ -7012,7 +7035,7 @@ func TestContext2Plan_removedResourceBasic(t *testing.T) { s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) s.SetResourceInstanceDeposed( mustResourceInstanceAddr(addr.String()), desposedKey, @@ -7022,6 +7045,7 @@ func TestContext2Plan_removedResourceBasic(t *testing.T) { Dependencies: []addrs.ConfigResource{}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) }) @@ -7092,7 +7116,7 @@ func TestContext2Plan_removedModuleBasic(t *testing.T) { s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) s.SetResourceInstanceDeposed( mustResourceInstanceAddr(addr.String()), desposedKey, @@ -7102,6 +7126,7 @@ func TestContext2Plan_removedModuleBasic(t *testing.T) { Dependencies: []addrs.ConfigResource{}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) }) @@ -7174,11 +7199,11 @@ func TestContext2Plan_removedModuleForgetsAllInstances(t *testing.T) { s.SetResourceInstanceCurrent(addrFirst, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) s.SetResourceInstanceCurrent(addrSecond, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -7240,11 +7265,11 @@ func TestContext2Plan_removedResourceForgetsAllInstances(t *testing.T) { s.SetResourceInstanceCurrent(addrFirst, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) s.SetResourceInstanceCurrent(addrSecond, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -7308,7 +7333,7 @@ func TestContext2Plan_removedResourceInChildModuleFromParentModule(t *testing.T) s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -7370,7 +7395,7 @@ func TestContext2Plan_removedResourceInChildModuleFromChildModule(t *testing.T) s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -7433,7 +7458,7 @@ func TestContext2Plan_removedResourceInGrandchildModuleFromRootModule(t *testing s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -7496,7 +7521,7 @@ func TestContext2Plan_removedChildModuleForgetsResourceInGrandchildModule(t *tes s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -7564,7 +7589,7 @@ func TestContext2Plan_movedAndRemovedResourceAtTheSameTime(t *testing.T) { s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -7629,7 +7654,7 @@ func TestContext2Plan_removedResourceButResourceBlockStillExists(t *testing.T) { s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -7678,7 +7703,7 @@ func TestContext2Plan_removedResourceButResourceBlockStillExistsInChildModule(t s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() @@ -7727,7 +7752,7 @@ func TestContext2Plan_removedModuleButModuleBlockStillExists(t *testing.T) { s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: states.ObjectReady, - }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) + }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), addrs.NoKey) }) p := simpleMockProvider() diff --git a/internal/tofu/context_plan_test.go b/internal/tofu/context_plan_test.go index dabf64bf41..a22466fc6d 100644 --- a/internal/tofu/context_plan_test.go +++ b/internal/tofu/context_plan_test.go @@ -95,6 +95,7 @@ func TestContext2Plan_createBefore_deposed(t *testing.T) { AttrsJSON: []byte(`{"id":"baz","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceDeposed( mustResourceInstanceAddr("aws_instance.foo").Resource, @@ -104,6 +105,7 @@ func TestContext2Plan_createBefore_deposed(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -810,6 +812,7 @@ func TestContext2Plan_moduleOrphans(t *testing.T) { AttrsJSON: []byte(`{"id":"baz"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -882,6 +885,7 @@ func TestContext2Plan_moduleOrphansWithProvisioner(t *testing.T) { AttrsJSON: []byte(`{"id":"top","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) child1 := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey).Child("child1", addrs.NoKey)) child1.SetResourceInstanceCurrent( @@ -891,6 +895,7 @@ func TestContext2Plan_moduleOrphansWithProvisioner(t *testing.T) { AttrsJSON: []byte(`{"id":"baz","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) child2 := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey).Child("child2", addrs.NoKey)) child2.SetResourceInstanceCurrent( @@ -900,6 +905,7 @@ func TestContext2Plan_moduleOrphansWithProvisioner(t *testing.T) { AttrsJSON: []byte(`{"id":"baz","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -1376,6 +1382,7 @@ func TestContext2Plan_preventDestroy_bad(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc123"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -1415,6 +1422,7 @@ func TestContext2Plan_preventDestroy_good(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc123","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -1446,6 +1454,7 @@ func TestContext2Plan_preventDestroy_countBad(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc123"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[1]").Resource, @@ -1454,6 +1463,7 @@ func TestContext2Plan_preventDestroy_countBad(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc345"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -1502,6 +1512,7 @@ func TestContext2Plan_preventDestroy_countGood(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc123"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[1]").Resource, @@ -1510,6 +1521,7 @@ func TestContext2Plan_preventDestroy_countGood(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc345"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -1553,6 +1565,7 @@ func TestContext2Plan_preventDestroy_countGoodNoChange(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc123","current":"0","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -1584,6 +1597,7 @@ func TestContext2Plan_preventDestroy_destroyPlan(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc123"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -1981,6 +1995,7 @@ func TestContext2Plan_dataResourceBecomesComputed(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc123","foo":"baz"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -2603,6 +2618,7 @@ func TestContext2Plan_countDecreaseToOne(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[1]").Resource, @@ -2611,6 +2627,7 @@ func TestContext2Plan_countDecreaseToOne(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[2]").Resource, @@ -2619,6 +2636,7 @@ func TestContext2Plan_countDecreaseToOne(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -2703,6 +2721,7 @@ func TestContext2Plan_countIncreaseFromNotSet(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","type":"aws_instance","foo":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -2780,6 +2799,7 @@ func TestContext2Plan_countIncreaseFromOne(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -2863,6 +2883,7 @@ func TestContext2Plan_countIncreaseFromOneCorrupted(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[0]").Resource, @@ -2871,6 +2892,7 @@ func TestContext2Plan_countIncreaseFromOneCorrupted(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -2971,6 +2993,7 @@ func TestContext2Plan_countIncreaseWithSplatReference(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","name":"foo 0"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[1]").Resource, @@ -2979,6 +3002,7 @@ func TestContext2Plan_countIncreaseWithSplatReference(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","name":"foo 1"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar[0]").Resource, @@ -2987,6 +3011,7 @@ func TestContext2Plan_countIncreaseWithSplatReference(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","foo_name":"foo 0"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar[1]").Resource, @@ -2995,6 +3020,7 @@ func TestContext2Plan_countIncreaseWithSplatReference(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","foo_name":"foo 1"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3135,6 +3161,7 @@ func TestContext2Plan_destroy(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.two").Resource, @@ -3143,6 +3170,7 @@ func TestContext2Plan_destroy(t *testing.T) { AttrsJSON: []byte(`{"id":"baz"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3196,6 +3224,7 @@ func TestContext2Plan_moduleDestroy(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) child.SetResourceInstanceCurrent( @@ -3205,6 +3234,7 @@ func TestContext2Plan_moduleDestroy(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3258,6 +3288,7 @@ func TestContext2Plan_moduleDestroyCycle(t *testing.T) { AttrsJSON: []byte(`{"id":"a"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) bModule := state.EnsureModule(addrs.RootModuleInstance.Child("b_module", addrs.NoKey)) bModule.SetResourceInstanceCurrent( @@ -3267,6 +3298,7 @@ func TestContext2Plan_moduleDestroyCycle(t *testing.T) { AttrsJSON: []byte(`{"id":"b"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3320,6 +3352,7 @@ func TestContext2Plan_moduleDestroyMultivar(t *testing.T) { AttrsJSON: []byte(`{"id":"bar0"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) child.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[1]").Resource, @@ -3328,6 +3361,7 @@ func TestContext2Plan_moduleDestroyMultivar(t *testing.T) { AttrsJSON: []byte(`{"id":"bar1"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3441,6 +3475,7 @@ func TestContext2Plan_diffVar(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","num":"2","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3556,6 +3591,7 @@ func TestContext2Plan_orphan(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3638,6 +3674,7 @@ func TestContext2Plan_state(t *testing.T) { AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ Providers: map[addrs.Provider]providers.Factory{ @@ -3740,6 +3777,7 @@ func TestContext2Plan_requiresReplace(t *testing.T) { AttrsJSON: []byte(`{"v":"hello"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3798,6 +3836,7 @@ func TestContext2Plan_taint(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","num":"2","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar").Resource, @@ -3806,6 +3845,7 @@ func TestContext2Plan_taint(t *testing.T) { AttrsJSON: []byte(`{"id":"baz"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3884,6 +3924,7 @@ func TestContext2Plan_taintIgnoreChanges(t *testing.T) { AttrsJSON: []byte(`{"id":"foo","vars":"foo","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -3948,6 +3989,7 @@ func TestContext2Plan_taintDestroyInterpolatedCountRace(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[1]").Resource, @@ -3956,6 +3998,7 @@ func TestContext2Plan_taintDestroyInterpolatedCountRace(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[2]").Resource, @@ -3964,6 +4007,7 @@ func TestContext2Plan_taintDestroyInterpolatedCountRace(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) for i := 0; i < 100; i++ { @@ -4314,6 +4358,7 @@ func TestContext2Plan_targetedOrphan(t *testing.T) { AttrsJSON: []byte(`{"id":"i-789xyz"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.nottargeted").Resource, @@ -4322,6 +4367,7 @@ func TestContext2Plan_targetedOrphan(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc123"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -4379,6 +4425,7 @@ func TestContext2Plan_excludedOrphan(t *testing.T) { AttrsJSON: []byte(`{"id":"i-789xyz"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.nottargeted").Resource, @@ -4387,6 +4434,7 @@ func TestContext2Plan_excludedOrphan(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc123"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -4445,6 +4493,7 @@ func TestContext2Plan_targetedModuleOrphan(t *testing.T) { AttrsJSON: []byte(`{"id":"i-789xyz"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) child.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.nottargeted").Resource, @@ -4453,6 +4502,7 @@ func TestContext2Plan_targetedModuleOrphan(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc123"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -4507,6 +4557,7 @@ func TestContext2Plan_excludedModuleOrphan(t *testing.T) { AttrsJSON: []byte(`{"id":"i-789xyz"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) child.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.nottargeted").Resource, @@ -4515,6 +4566,7 @@ func TestContext2Plan_excludedModuleOrphan(t *testing.T) { AttrsJSON: []byte(`{"id":"i-abc123"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -4790,6 +4842,7 @@ func TestContext2Plan_targetedOverTen(t *testing.T) { AttrsJSON: []byte(attrs), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) } @@ -4844,6 +4897,7 @@ func TestContext2Plan_excludedOverTen(t *testing.T) { AttrsJSON: []byte(attrs), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) } @@ -4942,6 +4996,7 @@ func TestContext2Plan_ignoreChanges(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","ami":"ami-abcd1234","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -5014,6 +5069,7 @@ func TestContext2Plan_ignoreChangesWildcard(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","ami":"ami-abcd1234","instance":"t2.micro","type":"aws_instance","foo":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -5079,6 +5135,7 @@ func TestContext2Plan_ignoreChangesInMap(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) m := testModule(t, "plan-ignore-changes-in-map") @@ -5136,6 +5193,7 @@ func TestContext2Plan_ignoreChangesSensitive(t *testing.T) { AttrsJSON: []byte(`{"id":"bar","ami":"ami-abcd1234","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -5527,6 +5585,7 @@ func TestContext2Plan_ignoreChangesWithFlatmaps(t *testing.T) { }`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -5598,6 +5657,7 @@ func TestContext2Plan_resourceNestedCount(t *testing.T) { AttrsJSON: []byte(`{"id":"foo0","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[1]").Resource, @@ -5606,6 +5666,7 @@ func TestContext2Plan_resourceNestedCount(t *testing.T) { AttrsJSON: []byte(`{"id":"foo1","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar[0]").Resource, @@ -5615,6 +5676,7 @@ func TestContext2Plan_resourceNestedCount(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.bar[1]").Resource, @@ -5624,6 +5686,7 @@ func TestContext2Plan_resourceNestedCount(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.baz[0]").Resource, @@ -5633,6 +5696,7 @@ func TestContext2Plan_resourceNestedCount(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.bar")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.baz[1]").Resource, @@ -5642,6 +5706,7 @@ func TestContext2Plan_resourceNestedCount(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.bar")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ Providers: map[addrs.Provider]providers.Factory{ @@ -6263,6 +6328,7 @@ resource "aws_instance" "foo" { AttrsJSON: []byte(`{"id":"child","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) state.EnsureModule(addrs.RootModuleInstance.Child("mod", addrs.IntKey(1))).SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo").Resource, @@ -6271,6 +6337,7 @@ resource "aws_instance" "foo" { AttrsJSON: []byte(`{"id":"child","type":"aws_instance"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) p := testProvider("aws") @@ -6672,6 +6739,7 @@ data "test_data_source" "foo" {} AttrsJSON: []byte(`{"id":"data_id", "foo":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -6721,6 +6789,7 @@ resource "test_instance" "b" { Dependencies: []addrs.ConfigResource{}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_instance.b").Resource, @@ -6730,6 +6799,7 @@ resource "test_instance" "b" { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_instance.a")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -7068,6 +7138,7 @@ resource "test_instance" "a" { Dependencies: []addrs.ConfigResource{}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -7240,6 +7311,7 @@ resource "test_instance" "a" { Dependencies: []addrs.ConfigResource{}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -7303,6 +7375,7 @@ resource "test_instance" "a" { Dependencies: []addrs.ConfigResource{}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -7361,6 +7434,7 @@ resource "test_instance" "a" { Dependencies: []addrs.ConfigResource{}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -7400,6 +7474,7 @@ resource "test_instance" "a" { Dependencies: []addrs.ConfigResource{}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) // the provider for this data source is no longer in the config, but that @@ -7412,6 +7487,7 @@ resource "test_instance" "a" { Dependencies: []addrs.ConfigResource{}, }, mustProviderConfig(`provider["registry.opentofu.org/local/test"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -7469,6 +7545,7 @@ resource "test_resource" "foo" { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) diff --git a/internal/tofu/context_refresh_test.go b/internal/tofu/context_refresh_test.go index 2b6bb78cd3..3287a109db 100644 --- a/internal/tofu/context_refresh_test.go +++ b/internal/tofu/context_refresh_test.go @@ -36,6 +36,7 @@ func TestContext2Refresh(t *testing.T) { AttrsJSON: []byte(`{"id":"foo","foo":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ @@ -98,6 +99,7 @@ func TestContext2Refresh_dynamicAttr(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) @@ -1435,6 +1437,7 @@ func TestContext2Refresh_orphanModule(t *testing.T) { }, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) child.SetResourceInstanceCurrent( @@ -1445,6 +1448,7 @@ func TestContext2Refresh_orphanModule(t *testing.T) { Dependencies: []addrs.ConfigResource{{Module: addrs.Module{"module.grandchild"}}}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) grandchild := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey).Child("grandchild", addrs.NoKey)) testSetResourceInstanceCurrent(grandchild, "aws_instance.baz", `{"id":"i-cde345"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) @@ -1574,6 +1578,7 @@ func TestContext2Refresh_schemaUpgradeFlatmap(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) @@ -1656,6 +1661,7 @@ func TestContext2Refresh_schemaUpgradeJSON(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) @@ -1793,6 +1799,7 @@ func TestRefresh_updateLifecycle(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) m := testModuleInline(t, map[string]string{ @@ -1846,6 +1853,7 @@ func TestContext2Refresh_dataSourceOrphan(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) p := testProvider("test") p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) { @@ -1936,6 +1944,7 @@ resource "test_resource" "foo" { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) ctx := testContext2(t, &ContextOpts{ diff --git a/internal/tofu/eval_context.go b/internal/tofu/eval_context.go index d74859799e..758f6685bb 100644 --- a/internal/tofu/eval_context.go +++ b/internal/tofu/eval_context.go @@ -44,7 +44,7 @@ type EvalContext interface { // It is an error to initialize the same provider more than once. This // method will panic if the module instance address of the given provider // configuration does not match the Path() of the EvalContext. - InitProvider(addr addrs.AbsProviderConfig) (providers.Interface, error) + InitProvider(addr addrs.AbsProviderConfig, key addrs.InstanceKey) (providers.Interface, error) // Provider gets the provider instance with the given address (already // initialized) or returns nil if the provider isn't initialized. @@ -53,7 +53,7 @@ type EvalContext interface { // resources in one module are able to use providers from other modules. // InitProvider must've been called on the EvalContext of the module // that owns the given provider before calling this method. - Provider(addrs.AbsProviderConfig) providers.Interface + Provider(addrs.AbsProviderConfig, addrs.InstanceKey) providers.Interface // ProviderSchema retrieves the schema for a particular provider, which // must have already been initialized with InitProvider. @@ -75,7 +75,7 @@ type EvalContext interface { // // This method will panic if the module instance address of the given // provider configuration does not match the Path() of the EvalContext. - ConfigureProvider(addrs.AbsProviderConfig, cty.Value) tfdiags.Diagnostics + ConfigureProvider(addrs.AbsProviderConfig, addrs.InstanceKey, cty.Value) tfdiags.Diagnostics // ProviderInput and SetProviderInput are used to configure providers // from user input. diff --git a/internal/tofu/eval_context_builtin.go b/internal/tofu/eval_context_builtin.go index fb9ff7791a..7910b5d47d 100644 --- a/internal/tofu/eval_context_builtin.go +++ b/internal/tofu/eval_context_builtin.go @@ -67,7 +67,7 @@ type BuiltinEvalContext struct { InputValue UIInput ProviderLock *sync.Mutex - ProviderCache map[string]providers.Interface + ProviderCache map[string]map[addrs.InstanceKey]providers.Interface ProviderInputConfig map[string]map[string]cty.Value ProvisionerLock *sync.Mutex @@ -128,14 +128,18 @@ func (ctx *BuiltinEvalContext) Input() UIInput { return ctx.InputValue } -func (ctx *BuiltinEvalContext) InitProvider(addr addrs.AbsProviderConfig) (providers.Interface, error) { +func (ctx *BuiltinEvalContext) InitProvider(addr addrs.AbsProviderConfig, providerKey addrs.InstanceKey) (providers.Interface, error) { ctx.ProviderLock.Lock() defer ctx.ProviderLock.Unlock() key := addr.String() + if ctx.ProviderCache[key] == nil { + ctx.ProviderCache[key] = make(map[addrs.InstanceKey]providers.Interface) + } + // If we have already initialized, it is an error - if _, ok := ctx.ProviderCache[key]; ok { + if _, ok := ctx.ProviderCache[key][providerKey]; ok { return nil, fmt.Errorf("%s is already initialized", addr) } @@ -156,17 +160,22 @@ func (ctx *BuiltinEvalContext) InitProvider(addr addrs.AbsProviderConfig) (provi } } - log.Printf("[TRACE] BuiltinEvalContext: Initialized %q provider for %s", addr.String(), addr) - ctx.ProviderCache[key] = p + log.Printf("[TRACE] BuiltinEvalContext: Initialized %q%s provider for %s", addr.String(), providerKey, addr) + ctx.ProviderCache[key][providerKey] = p return p, nil } -func (ctx *BuiltinEvalContext) Provider(addr addrs.AbsProviderConfig) providers.Interface { +func (ctx *BuiltinEvalContext) Provider(addr addrs.AbsProviderConfig, key addrs.InstanceKey) providers.Interface { ctx.ProviderLock.Lock() defer ctx.ProviderLock.Unlock() - return ctx.ProviderCache[addr.String()] + pm, ok := ctx.ProviderCache[addr.String()] + if !ok { + return nil + } + + return pm[key] } func (ctx *BuiltinEvalContext) ProviderSchema(addr addrs.AbsProviderConfig) (providers.ProviderSchema, error) { @@ -177,17 +186,27 @@ func (ctx *BuiltinEvalContext) CloseProvider(addr addrs.AbsProviderConfig) error ctx.ProviderLock.Lock() defer ctx.ProviderLock.Unlock() + var diags tfdiags.Diagnostics + key := addr.String() - provider := ctx.ProviderCache[key] - if provider != nil { + providerMap := ctx.ProviderCache[key] + if providerMap != nil { + for _, provider := range providerMap { + err := provider.Close() + if err != nil { + diags = diags.Append(err) + } + } delete(ctx.ProviderCache, key) - return provider.Close() + } + if diags.HasErrors() { + return diags.Err() } return nil } -func (ctx *BuiltinEvalContext) ConfigureProvider(addr addrs.AbsProviderConfig, cfg cty.Value) tfdiags.Diagnostics { +func (ctx *BuiltinEvalContext) ConfigureProvider(addr addrs.AbsProviderConfig, providerKey addrs.InstanceKey, cfg cty.Value) tfdiags.Diagnostics { var diags tfdiags.Diagnostics if !addr.Module.Equal(ctx.Path().Module()) { // This indicates incorrect use of ConfigureProvider: it should be used @@ -195,9 +214,9 @@ func (ctx *BuiltinEvalContext) ConfigureProvider(addr addrs.AbsProviderConfig, c panic(fmt.Sprintf("%s configured by wrong module %s", addr, ctx.Path())) } - p := ctx.Provider(addr) + p := ctx.Provider(addr, providerKey) if p == nil { - diags = diags.Append(fmt.Errorf("%s not initialized", addr)) + diags = diags.Append(fmt.Errorf("%s not initialized", addr.InstanceString(providerKey))) return diags } @@ -433,7 +452,7 @@ func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, source } scope := ctx.Evaluator.Scope(data, self, source, func(pf addrs.ProviderFunction, rng tfdiags.SourceRange) (*function.Function, tfdiags.Diagnostics) { - absPc, ok := ctx.ProviderFunctionTracker.Lookup(ctx.PathValue.Module(), pf) + providedBy, ok := ctx.ProviderFunctionTracker.Lookup(ctx.PathValue.Module(), pf) if !ok { // This should not be possible if references are tracked correctly return nil, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{ @@ -444,14 +463,28 @@ func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, source }) } - provider := ctx.Provider(absPc) + var providerKey addrs.InstanceKey + if providedBy.KeyExpression != nil && ctx.Evaluator.Operation != walkValidate { + moduleInstanceForKey := ctx.PathValue[:len(providedBy.KeyModule)] + if !moduleInstanceForKey.Module().Equal(providedBy.KeyModule) { + panic(fmt.Sprintf("Invalid module key expression location %s in function %s", providedBy.KeyModule, pf.String())) + } + + var keyDiags tfdiags.Diagnostics + providerKey, keyDiags = resolveProviderModuleInstance(ctx, providedBy.KeyExpression, moduleInstanceForKey, ctx.PathValue.String()+" "+pf.String()) + if keyDiags.HasErrors() { + return nil, keyDiags + } + } + + provider := ctx.Provider(providedBy.Provider, providerKey) if provider == nil { // This should not be possible if references are tracked correctly return nil, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, - Summary: "BUG: Uninitialized function provider", - Detail: fmt.Sprintf("Provider %q has not yet been initialized", absPc.String()), + Summary: "Uninitialized function provider", + Detail: fmt.Sprintf("Provider %q has not yet been initialized", providedBy.Provider.String()), Subject: rng.ToHCL().Ptr(), }) } diff --git a/internal/tofu/eval_context_builtin_test.go b/internal/tofu/eval_context_builtin_test.go index 98a5f7bc90..865c54542f 100644 --- a/internal/tofu/eval_context_builtin_test.go +++ b/internal/tofu/eval_context_builtin_test.go @@ -63,7 +63,7 @@ func TestBuildingEvalContextInitProvider(t *testing.T) { ctx := testBuiltinEvalContext(t) ctx = ctx.WithPath(addrs.RootModuleInstance).(*BuiltinEvalContext) ctx.ProviderLock = &lock - ctx.ProviderCache = make(map[string]providers.Interface) + ctx.ProviderCache = make(map[string]map[addrs.InstanceKey]providers.Interface) ctx.Plugins = newContextPlugins(map[addrs.Provider]providers.Factory{ addrs.NewDefaultProvider("test"): providers.FactoryFixed(testP), }, nil) @@ -78,11 +78,11 @@ func TestBuildingEvalContextInitProvider(t *testing.T) { Alias: "foo", } - _, err := ctx.InitProvider(providerAddrDefault) + _, err := ctx.InitProvider(providerAddrDefault, addrs.NoKey) if err != nil { t.Fatalf("error initializing provider test: %s", err) } - _, err = ctx.InitProvider(providerAddrAlias) + _, err = ctx.InitProvider(providerAddrAlias, addrs.NoKey) if err != nil { t.Fatalf("error initializing provider test.foo: %s", err) } diff --git a/internal/tofu/eval_context_mock.go b/internal/tofu/eval_context_mock.go index 522d00ba49..fbb97de79b 100644 --- a/internal/tofu/eval_context_mock.go +++ b/internal/tofu/eval_context_mock.go @@ -183,14 +183,14 @@ func (c *MockEvalContext) Input() UIInput { return c.InputInput } -func (c *MockEvalContext) InitProvider(addr addrs.AbsProviderConfig) (providers.Interface, error) { +func (c *MockEvalContext) InitProvider(addr addrs.AbsProviderConfig, _ addrs.InstanceKey) (providers.Interface, error) { c.InitProviderCalled = true c.InitProviderType = addr.String() c.InitProviderAddr = addr return c.InitProviderProvider, c.InitProviderError } -func (c *MockEvalContext) Provider(addr addrs.AbsProviderConfig) providers.Interface { +func (c *MockEvalContext) Provider(addr addrs.AbsProviderConfig, _ addrs.InstanceKey) providers.Interface { c.ProviderCalled = true c.ProviderAddr = addr return c.ProviderProvider @@ -208,8 +208,7 @@ func (c *MockEvalContext) CloseProvider(addr addrs.AbsProviderConfig) error { return nil } -func (c *MockEvalContext) ConfigureProvider(addr addrs.AbsProviderConfig, cfg cty.Value) tfdiags.Diagnostics { - +func (c *MockEvalContext) ConfigureProvider(addr addrs.AbsProviderConfig, _ addrs.InstanceKey, cfg cty.Value) tfdiags.Diagnostics { c.ConfigureProviderCalled = true c.ConfigureProviderAddr = addr c.ConfigureProviderConfig = cfg diff --git a/internal/tofu/eval_provider.go b/internal/tofu/eval_provider.go index 26f428d1b8..f0bd4dc2d0 100644 --- a/internal/tofu/eval_provider.go +++ b/internal/tofu/eval_provider.go @@ -15,7 +15,11 @@ import ( "github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/configs" "github.com/opentofu/opentofu/internal/configs/hcl2shim" + "github.com/opentofu/opentofu/internal/lang" + "github.com/opentofu/opentofu/internal/lang/evalchecks" + "github.com/opentofu/opentofu/internal/lang/marks" "github.com/opentofu/opentofu/internal/providers" + "github.com/opentofu/opentofu/internal/tfdiags" ) func buildProviderConfig(ctx EvalContext, addr addrs.AbsProviderConfig, config *configs.Provider) hcl.Body { @@ -47,15 +51,75 @@ func buildProviderConfig(ctx EvalContext, addr addrs.AbsProviderConfig, config * } } +func resolveProviderResourceInstance(ctx EvalContext, keyExpr hcl.Expression, resourcePath addrs.AbsResourceInstance) (addrs.InstanceKey, tfdiags.Diagnostics) { + keyData := ctx.InstanceExpander().GetResourceInstanceRepetitionData(resourcePath) + keyScope := ctx.EvaluationScope(nil, nil, keyData) + return resolveProviderInstance(keyExpr, keyScope, resourcePath.String()) +} + +func resolveProviderModuleInstance(ctx EvalContext, keyExpr hcl.Expression, modulePath addrs.ModuleInstance, source string) (addrs.InstanceKey, tfdiags.Diagnostics) { + keyData := ctx.InstanceExpander().GetModuleInstanceRepetitionData(modulePath) + keyScope := ctx.WithPath(modulePath).EvaluationScope(nil, nil, keyData) + return resolveProviderInstance(keyExpr, keyScope, source) +} + +func resolveProviderInstance(keyExpr hcl.Expression, keyScope *lang.Scope, source string) (addrs.InstanceKey, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + + keyVal, keyDiags := keyScope.EvalExpr(keyExpr, cty.DynamicPseudoType) + diags = diags.Append(keyDiags) + if keyDiags.HasErrors() { + return nil, diags + } + + if keyVal.HasMark(marks.Sensitive) { + return nil, diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid provider instance key", + Detail: "A provider instance key must not be derived from a sensitive value.", + Subject: keyExpr.Range().Ptr(), + Extra: evalchecks.DiagnosticCausedBySensitive(true), + }) + } + if keyVal.IsNull() { + return nil, diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid provider instance key", + Detail: "A provider instance key must not be null.", + Subject: keyExpr.Range().Ptr(), + }) + } + if !keyVal.IsKnown() { + return nil, diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid provider instance key", + Detail: fmt.Sprintf("The provider instance key for %s depends on values that cannot be determined until apply, and so OpenTofu cannot select a provider instance to create a plan for this resource instance.", source), + Subject: keyExpr.Range().Ptr(), + Extra: evalchecks.DiagnosticCausedByUnknown(true), + }) + } + + parsedKey, parsedErr := addrs.ParseInstanceKey(keyVal) + if parsedErr != nil { + return nil, diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid provider instance key", + Detail: fmt.Sprintf("The given instance key is unsuitable: %s.", tfdiags.FormatError(parsedErr)), + Subject: keyExpr.Range().Ptr(), + }) + } + return parsedKey, diags +} + // getProvider returns the providers.Interface and schema for a given provider. -func getProvider(ctx EvalContext, addr addrs.AbsProviderConfig) (providers.Interface, providers.ProviderSchema, error) { +func getProvider(ctx EvalContext, addr addrs.AbsProviderConfig, providerKey addrs.InstanceKey) (providers.Interface, providers.ProviderSchema, error) { if addr.Provider.Type == "" { // Should never happen panic("GetProvider used with uninitialized provider configuration address") } - provider := ctx.Provider(addr) + provider := ctx.Provider(addr, providerKey) if provider == nil { - return nil, providers.ProviderSchema{}, fmt.Errorf("provider %s not initialized", addr) + return nil, providers.ProviderSchema{}, fmt.Errorf("provider %s not initialized", addr.InstanceString(providerKey)) } // Not all callers require a schema, so we will leave checking for a nil // schema to the callers. diff --git a/internal/tofu/evaluate_test.go b/internal/tofu/evaluate_test.go index bb994f614f..3898bb05b7 100644 --- a/internal/tofu/evaluate_test.go +++ b/internal/tofu/evaluate_test.go @@ -246,6 +246,7 @@ func TestEvaluatorGetResource(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }).SyncWrapper() @@ -420,6 +421,7 @@ func TestEvaluatorGetResource_changes(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }).SyncWrapper() diff --git a/internal/tofu/graph_builder_apply_test.go b/internal/tofu/graph_builder_apply_test.go index d800f1da5e..a54d12291d 100644 --- a/internal/tofu/graph_builder_apply_test.go +++ b/internal/tofu/graph_builder_apply_test.go @@ -104,6 +104,7 @@ func TestApplyGraphBuilder_depCbd(t *testing.T) { AttrsJSON: []byte(`{"id":"A"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.B").Resource, @@ -113,6 +114,7 @@ func TestApplyGraphBuilder_depCbd(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) b := &ApplyGraphBuilder{ @@ -270,6 +272,7 @@ func TestApplyGraphBuilder_destroyStateOnly(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) child.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.B").Resource, @@ -279,6 +282,7 @@ func TestApplyGraphBuilder_destroyStateOnly(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.test_object.A")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) b := &ApplyGraphBuilder{ @@ -332,6 +336,7 @@ func TestApplyGraphBuilder_destroyCount(t *testing.T) { AttrsJSON: []byte(`{"id":"B"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.B").Resource, @@ -341,6 +346,7 @@ func TestApplyGraphBuilder_destroyCount(t *testing.T) { Dependencies: []addrs.ConfigResource{addrA.ContainingResource().Config()}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) b := &ApplyGraphBuilder{ @@ -393,6 +399,7 @@ func TestApplyGraphBuilder_moduleDestroy(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) modB := state.EnsureModule(addrs.RootModuleInstance.Child("B", addrs.NoKey)) modB.SetResourceInstanceCurrent( @@ -403,6 +410,7 @@ func TestApplyGraphBuilder_moduleDestroy(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.A.test_object.foo")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) b := &ApplyGraphBuilder{ @@ -546,6 +554,7 @@ func TestApplyGraphBuilder_updateFromOrphan(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) root.SetResourceInstanceCurrent( addrs.Resource{ @@ -571,6 +580,7 @@ func TestApplyGraphBuilder_updateFromOrphan(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) b := &ApplyGraphBuilder{ @@ -649,6 +659,7 @@ func TestApplyGraphBuilder_updateFromCBDOrphan(t *testing.T) { CreateBeforeDestroy: true, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( addrs.Resource{ @@ -671,6 +682,7 @@ func TestApplyGraphBuilder_updateFromCBDOrphan(t *testing.T) { }, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) b := &ApplyGraphBuilder{ @@ -721,6 +733,7 @@ func TestApplyGraphBuilder_orphanedWithProvider(t *testing.T) { AttrsJSON: []byte(`{"id":"A"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"].foo`), + addrs.NoKey, ) b := &ApplyGraphBuilder{ diff --git a/internal/tofu/graph_walk_context.go b/internal/tofu/graph_walk_context.go index a2ad471503..6029034cee 100644 --- a/internal/tofu/graph_walk_context.go +++ b/internal/tofu/graph_walk_context.go @@ -60,7 +60,7 @@ type ContextGraphWalker struct { variableValues map[string]map[string]cty.Value providerLock sync.Mutex - providerCache map[string]providers.Interface + providerCache map[string]map[addrs.InstanceKey]providers.Interface provisionerLock sync.Mutex provisionerCache map[string]provisioners.Interface @@ -129,7 +129,7 @@ func (w *ContextGraphWalker) EvalContext() EvalContext { func (w *ContextGraphWalker) init() { w.contexts = make(map[string]*BuiltinEvalContext) - w.providerCache = make(map[string]providers.Interface) + w.providerCache = make(map[string]map[addrs.InstanceKey]providers.Interface) w.provisionerCache = make(map[string]provisioners.Interface) w.variableValues = make(map[string]map[string]cty.Value) diff --git a/internal/tofu/node_data_destroy.go b/internal/tofu/node_data_destroy.go index 8150e0632b..ee03f210d1 100644 --- a/internal/tofu/node_data_destroy.go +++ b/internal/tofu/node_data_destroy.go @@ -24,6 +24,6 @@ var ( // GraphNodeExecutable func (n *NodeDestroyableDataResourceInstance) Execute(ctx EvalContext, op walkOperation) tfdiags.Diagnostics { log.Printf("[TRACE] NodeDestroyableDataResourceInstance: removing state object for %s", n.Addr) - ctx.State().SetResourceInstanceCurrent(n.Addr, nil, n.ResolvedProvider) + ctx.State().SetResourceInstanceCurrent(n.Addr, nil, n.ResolvedProvider.ProviderConfig, nil) return nil } diff --git a/internal/tofu/node_data_destroy_test.go b/internal/tofu/node_data_destroy_test.go index 2f12320017..ec4f7e6a87 100644 --- a/internal/tofu/node_data_destroy_test.go +++ b/internal/tofu/node_data_destroy_test.go @@ -28,6 +28,7 @@ func TestNodeDataDestroyExecute(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) ctx := &MockEvalContext{ StateState: state.SyncWrapper(), diff --git a/internal/tofu/node_module_expand.go b/internal/tofu/node_module_expand.go index 8abd92abc1..bf336084f2 100644 --- a/internal/tofu/node_module_expand.go +++ b/internal/tofu/node_module_expand.go @@ -73,6 +73,14 @@ func (n *nodeExpandModule) References() []*addrs.Reference { forEachRefs, _ := lang.ReferencesInExpr(addrs.ParseRef, n.ModuleCall.ForEach) refs = append(refs, forEachRefs...) } + + for _, passed := range n.ModuleCall.Providers { + if passed.InParent.KeyExpression != nil { + providerRefs, _ := lang.ReferencesInExpr(addrs.ParseRef, passed.InParent.KeyExpression) + refs = append(refs, providerRefs...) + } + } + return refs } diff --git a/internal/tofu/node_provider.go b/internal/tofu/node_provider.go index 9bcbb1768d..d45d37c672 100644 --- a/internal/tofu/node_provider.go +++ b/internal/tofu/node_provider.go @@ -10,6 +10,7 @@ import ( "log" "github.com/hashicorp/hcl/v2" + "github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/configs/configschema" "github.com/opentofu/opentofu/internal/providers" "github.com/opentofu/opentofu/internal/tfdiags" @@ -26,34 +27,74 @@ var ( ) // GraphNodeExecutable -func (n *NodeApplyableProvider) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) { - _, err := ctx.InitProvider(n.Addr) - diags = diags.Append(err) - if diags.HasErrors() { - return diags - } - provider, _, err := getProvider(ctx, n.Addr) - diags = diags.Append(err) - if diags.HasErrors() { - return diags +func (n *NodeApplyableProvider) Execute(ctx EvalContext, op walkOperation) tfdiags.Diagnostics { + instances, diags := n.initInstances(ctx, op) + + for key, provider := range instances { + diags = diags.Append(n.executeInstance(ctx, op, key, provider)) } + return diags +} +func (n *NodeApplyableProvider) initInstances(ctx EvalContext, op walkOperation) (map[addrs.InstanceKey]providers.Interface, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + + var initKeys []addrs.InstanceKey + // config -> init (different due to validate skipping most for_each logic) + instanceKeys := make(map[addrs.InstanceKey]addrs.InstanceKey) + if n.Config == nil || n.Config.Instances == nil { + initKeys = append(initKeys, addrs.NoKey) + instanceKeys[addrs.NoKey] = addrs.NoKey + } else if op == walkValidate { + // Instances are set AND we are validating + initKeys = append(initKeys, addrs.NoKey) + for key := range n.Config.Instances { + instanceKeys[key] = addrs.NoKey + } + } else { + // Instances are set AND we are not validating + for key := range n.Config.Instances { + initKeys = append(initKeys, key) + instanceKeys[key] = key + } + } + + for _, key := range initKeys { + _, err := ctx.InitProvider(n.Addr, key) + diags = diags.Append(err) + } + if diags.HasErrors() { + return nil, diags + } + + instances := make(map[addrs.InstanceKey]providers.Interface) + for configKey, initKey := range instanceKeys { + provider, _, err := getProvider(ctx, n.Addr, initKey) + diags = diags.Append(err) + instances[configKey] = provider + } + if diags.HasErrors() { + return nil, diags + } + + return instances, diags +} +func (n *NodeApplyableProvider) executeInstance(ctx EvalContext, op walkOperation, providerKey addrs.InstanceKey, provider providers.Interface) tfdiags.Diagnostics { switch op { case walkValidate: log.Printf("[TRACE] NodeApplyableProvider: validating configuration for %s", n.Addr) - return diags.Append(n.ValidateProvider(ctx, provider)) + return n.ValidateProvider(ctx, providerKey, provider) case walkPlan, walkPlanDestroy, walkApply, walkDestroy: log.Printf("[TRACE] NodeApplyableProvider: configuring %s", n.Addr) - return diags.Append(n.ConfigureProvider(ctx, provider, false)) + return n.ConfigureProvider(ctx, providerKey, provider, false) case walkImport: log.Printf("[TRACE] NodeApplyableProvider: configuring %s (requiring that configuration is wholly known)", n.Addr) - return diags.Append(n.ConfigureProvider(ctx, provider, true)) + return n.ConfigureProvider(ctx, providerKey, provider, true) } - return diags + return nil } -func (n *NodeApplyableProvider) ValidateProvider(ctx EvalContext, provider providers.Interface) (diags tfdiags.Diagnostics) { - +func (n *NodeApplyableProvider) ValidateProvider(ctx EvalContext, providerKey addrs.InstanceKey, provider providers.Interface) tfdiags.Diagnostics { configBody := buildProviderConfig(ctx, n.Addr, n.ProviderConfig()) // if a provider config is empty (only an alias), return early and don't continue @@ -65,7 +106,7 @@ func (n *NodeApplyableProvider) ValidateProvider(ctx EvalContext, provider provi } schemaResp := provider.GetProviderSchema() - diags = diags.Append(schemaResp.Diagnostics.InConfigBody(configBody, n.Addr.String())) + diags := schemaResp.Diagnostics.InConfigBody(configBody, n.Addr.String()) if diags.HasErrors() { return diags } @@ -79,6 +120,9 @@ func (n *NodeApplyableProvider) ValidateProvider(ctx EvalContext, provider provi } data := EvalDataForNoInstanceKey + if n.Config != nil && n.Config.Instances != nil { + data = n.Config.Instances[providerKey] + } configVal, _, evalDiags := ctx.EvaluateBlock(configBody, configSchema, nil, data) if evalDiags.HasErrors() { @@ -103,19 +147,22 @@ func (n *NodeApplyableProvider) ValidateProvider(ctx EvalContext, provider provi // ConfigureProvider configures a provider that is already initialized and retrieved. // If verifyConfigIsKnown is true, ConfigureProvider will return an error if the // provider configVal is not wholly known and is meant only for use during import. -func (n *NodeApplyableProvider) ConfigureProvider(ctx EvalContext, provider providers.Interface, verifyConfigIsKnown bool) (diags tfdiags.Diagnostics) { +func (n *NodeApplyableProvider) ConfigureProvider(ctx EvalContext, providerKey addrs.InstanceKey, provider providers.Interface, verifyConfigIsKnown bool) tfdiags.Diagnostics { config := n.ProviderConfig() configBody := buildProviderConfig(ctx, n.Addr, config) resp := provider.GetProviderSchema() - diags = diags.Append(resp.Diagnostics.InConfigBody(configBody, n.Addr.String())) + diags := resp.Diagnostics.InConfigBody(configBody, n.Addr.String()) if diags.HasErrors() { return diags } configSchema := resp.Provider.Block data := EvalDataForNoInstanceKey + if n.Config != nil && n.Config.Instances != nil { + data = n.Config.Instances[providerKey] + } configVal, configBody, evalDiags := ctx.EvaluateBlock(configBody, configSchema, nil, data) diags = diags.Append(evalDiags) @@ -169,7 +216,7 @@ func (n *NodeApplyableProvider) ConfigureProvider(ctx EvalContext, provider prov log.Printf("[WARN] ValidateProviderConfig from %q changed the config value, but that value is unused", n.Addr) } - configDiags := ctx.ConfigureProvider(n.Addr, unmarkedConfigVal) + configDiags := ctx.ConfigureProvider(n.Addr, providerKey, unmarkedConfigVal) diags = diags.Append(configDiags.InConfigBody(configBody, n.Addr.String())) if diags.HasErrors() && config == nil { // If there isn't an explicit "provider" block in the configuration, diff --git a/internal/tofu/node_provider_eval.go b/internal/tofu/node_provider_eval.go index 35e6216916..6f29387d37 100644 --- a/internal/tofu/node_provider_eval.go +++ b/internal/tofu/node_provider_eval.go @@ -5,7 +5,10 @@ package tofu -import "github.com/opentofu/opentofu/internal/tfdiags" +import ( + "github.com/opentofu/opentofu/internal/addrs" + "github.com/opentofu/opentofu/internal/tfdiags" +) // NodeEvalableProvider represents a provider during an "eval" walk. // This special provider node type just initializes a provider and @@ -19,6 +22,6 @@ var _ GraphNodeExecutable = (*NodeEvalableProvider)(nil) // GraphNodeExecutable func (n *NodeEvalableProvider) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) { - _, err := ctx.InitProvider(n.Addr) + _, err := ctx.InitProvider(n.Addr, addrs.NoKey) return diags.Append(err) } diff --git a/internal/tofu/node_provider_test.go b/internal/tofu/node_provider_test.go index 719eda6867..517b12cf0c 100644 --- a/internal/tofu/node_provider_test.go +++ b/internal/tofu/node_provider_test.go @@ -287,7 +287,7 @@ func TestNodeApplyableProvider_Validate(t *testing.T) { }, } - diags := node.ValidateProvider(ctx, provider) + diags := node.ValidateProvider(ctx, addrs.NoKey, provider) if diags.HasErrors() { t.Errorf("unexpected error with valid config: %s", diags.Err()) } @@ -308,7 +308,7 @@ func TestNodeApplyableProvider_Validate(t *testing.T) { }, } - diags := node.ValidateProvider(ctx, provider) + diags := node.ValidateProvider(ctx, addrs.NoKey, provider) if !diags.HasErrors() { t.Error("missing expected error with invalid config") } @@ -321,7 +321,7 @@ func TestNodeApplyableProvider_Validate(t *testing.T) { }, } - diags := node.ValidateProvider(ctx, provider) + diags := node.ValidateProvider(ctx, addrs.NoKey, provider) if diags.HasErrors() { t.Errorf("unexpected error with empty config: %s", diags.Err()) } @@ -369,7 +369,7 @@ func TestNodeApplyableProvider_ConfigProvider(t *testing.T) { }, } - diags := node.ConfigureProvider(ctx, provider, false) + diags := node.ConfigureProvider(ctx, addrs.NoKey, provider, false) if diags.HasErrors() { t.Errorf("unexpected error with valid config: %s", diags.Err()) } @@ -382,7 +382,7 @@ func TestNodeApplyableProvider_ConfigProvider(t *testing.T) { }, } - diags := node.ConfigureProvider(ctx, provider, false) + diags := node.ConfigureProvider(ctx, addrs.NoKey, provider, false) if !diags.HasErrors() { t.Fatal("missing expected error with nil config") } @@ -403,7 +403,7 @@ func TestNodeApplyableProvider_ConfigProvider(t *testing.T) { }, } - diags := node.ConfigureProvider(ctx, provider, false) + diags := node.ConfigureProvider(ctx, addrs.NoKey, provider, false) if !diags.HasErrors() { t.Fatal("missing expected error with invalid config") } @@ -458,7 +458,7 @@ func TestNodeApplyableProvider_ConfigProvider_config_fn_err(t *testing.T) { }, } - diags := node.ConfigureProvider(ctx, provider, false) + diags := node.ConfigureProvider(ctx, addrs.NoKey, provider, false) if diags.HasErrors() { t.Errorf("unexpected error with valid config: %s", diags.Err()) } @@ -471,7 +471,7 @@ func TestNodeApplyableProvider_ConfigProvider_config_fn_err(t *testing.T) { }, } - diags := node.ConfigureProvider(ctx, provider, false) + diags := node.ConfigureProvider(ctx, addrs.NoKey, provider, false) if !diags.HasErrors() { t.Fatal("missing expected error with nil config") } @@ -492,7 +492,7 @@ func TestNodeApplyableProvider_ConfigProvider_config_fn_err(t *testing.T) { }, } - diags := node.ConfigureProvider(ctx, provider, false) + diags := node.ConfigureProvider(ctx, addrs.NoKey, provider, false) if !diags.HasErrors() { t.Fatal("missing expected error with invalid config") } @@ -518,7 +518,7 @@ func TestGetSchemaError(t *testing.T) { }, } - diags := node.ConfigureProvider(ctx, provider, false) + diags := node.ConfigureProvider(ctx, addrs.NoKey, provider, false) for _, d := range diags { desc := d.Description() if desc.Address != providerAddr.String() { diff --git a/internal/tofu/node_resource_abstract.go b/internal/tofu/node_resource_abstract.go index 398c67e84b..ead8a6d943 100644 --- a/internal/tofu/node_resource_abstract.go +++ b/internal/tofu/node_resource_abstract.go @@ -77,11 +77,12 @@ type NodeAbstractResource struct { forceDependsOn bool // The address of the provider this resource will use - ResolvedProvider addrs.AbsProviderConfig + ResolvedProvider ResolvedProvider + // storedProviderConfig is the provider address retrieved from the // state. This is defined here for access within the ProvidedBy method, but // will be set from the embedding instance type when the state is attached. - storedProviderConfig addrs.AbsProviderConfig + storedProviderConfig ResolvedProvider // This resource may expand into instances which need to be imported. importTargets []*ImportTarget @@ -293,28 +294,48 @@ func (n *NodeAbstractResource) DependsOn() []*addrs.Reference { return result } -func (n *NodeAbstractResource) SetProvider(p addrs.AbsProviderConfig) { - n.ResolvedProvider = p +// GraphNodeProviderConsumer +func (n *NodeAbstractResource) SetProvider(resolved ResolvedProvider) { + n.ResolvedProvider = resolved } // GraphNodeProviderConsumer -func (n *NodeAbstractResource) ProvidedBy() addrs.ProviderConfig { +func (n *NodeAbstractResource) ProvidedBy() RequestedProvider { // Once the provider is fully resolved, we can return the known value. - if n.ResolvedProvider.Provider.Type != "" { - return n.ResolvedProvider + if n.ResolvedProvider.ProviderConfig.Provider.Type != "" { + return RequestedProvider{ + ProviderConfig: n.ResolvedProvider.ProviderConfig, + KeyExpression: n.ResolvedProvider.KeyExpression, + KeyModule: n.ResolvedProvider.KeyModule, + KeyResource: n.ResolvedProvider.KeyResource, + KeyExact: n.ResolvedProvider.KeyExact, + } } // If we have a config we prefer that above all else if n.Config != nil { - return n.Config.ProviderConfigAddr() + result := RequestedProvider{ + ProviderConfig: n.Config.ProviderConfigAddr(), + } + if n.Config.ProviderConfigRef != nil && n.Config.ProviderConfigRef.KeyExpression != nil { + result.KeyResource = true + result.KeyExpression = n.Config.ProviderConfigRef.KeyExpression + } + return result } // See if we have a valid provider config from the state. - if n.storedProviderConfig.Provider.Type != "" { + if n.storedProviderConfig.ProviderConfig.Provider.Type != "" { // An address from the state must match exactly, since we must ensure // we refresh/destroy a resource with the same provider configuration // that created it. - return n.storedProviderConfig + return RequestedProvider{ + ProviderConfig: n.storedProviderConfig.ProviderConfig, + KeyExpression: n.storedProviderConfig.KeyExpression, + KeyModule: n.storedProviderConfig.KeyModule, + KeyResource: n.storedProviderConfig.KeyResource, + KeyExact: n.storedProviderConfig.KeyExact, + } } // We might have an import target that is providing a specific provider, @@ -325,29 +346,34 @@ func (n *NodeAbstractResource) ProvidedBy() addrs.ProviderConfig { // of them should be. They should also all have the same provider, so it // shouldn't matter which we check here, as they'll all give the same. if n.importTargets[0].Config != nil && n.importTargets[0].Config.ProviderConfigRef != nil { - return addrs.LocalProviderConfig{ - LocalName: n.importTargets[0].Config.ProviderConfigRef.Name, - Alias: n.importTargets[0].Config.ProviderConfigRef.Alias, + return RequestedProvider{ + ProviderConfig: addrs.LocalProviderConfig{ + LocalName: n.importTargets[0].Config.ProviderConfigRef.Name, + Alias: n.importTargets[0].Config.ProviderConfigRef.Alias, + }, + // This is where we would specify a key expression if that was supported for import blocks } } } // No provider configuration found; return a default address - return addrs.LocalProviderConfig{ - LocalName: n.Addr.Resource.ImpliedProvider(), // Unused, see ProviderTransformer + return RequestedProvider{ + ProviderConfig: addrs.LocalProviderConfig{ + LocalName: n.Addr.Resource.ImpliedProvider(), // Unused, see ProviderTransformer + }, } } // GraphNodeProviderConsumer func (n *NodeAbstractResource) Provider() addrs.Provider { - if n.ResolvedProvider.Provider.Type != "" { - return n.ResolvedProvider.Provider + if n.ResolvedProvider.ProviderConfig.Provider.Type != "" { + return n.ResolvedProvider.ProviderConfig.Provider } if n.Config != nil { return n.Config.Provider } - if n.storedProviderConfig.Provider.Type != "" { - return n.storedProviderConfig.Provider + if n.storedProviderConfig.ProviderConfig.Provider.Type != "" { + return n.storedProviderConfig.ProviderConfig.Provider } if len(n.importTargets) > 0 { @@ -460,7 +486,7 @@ func (n *NodeAbstractResource) writeResourceState(ctx EvalContext, addr addrs.Ab return diags } - state.SetResourceProvider(addr, n.ResolvedProvider) + state.SetResourceProvider(addr, n.ResolvedProvider.ProviderConfig) expander.SetResourceCount(addr.Module, n.Addr.Resource, count) case n.Config != nil && n.Config.ForEach != nil: @@ -472,11 +498,11 @@ func (n *NodeAbstractResource) writeResourceState(ctx EvalContext, addr addrs.Ab // This method takes care of all of the business logic of updating this // while ensuring that any existing instances are preserved, etc. - state.SetResourceProvider(addr, n.ResolvedProvider) + state.SetResourceProvider(addr, n.ResolvedProvider.ProviderConfig) expander.SetResourceForEach(addr.Module, n.Addr.Resource, forEach) default: - state.SetResourceProvider(addr, n.ResolvedProvider) + state.SetResourceProvider(addr, n.ResolvedProvider.ProviderConfig) expander.SetResourceSingle(addr.Module, n.Addr.Resource) } @@ -485,9 +511,9 @@ func (n *NodeAbstractResource) writeResourceState(ctx EvalContext, addr addrs.Ab // readResourceInstanceState reads the current object for a specific instance in // the state. -func (n *NodeAbstractResource) readResourceInstanceState(ctx EvalContext, addr addrs.AbsResourceInstance) (*states.ResourceInstanceObject, tfdiags.Diagnostics) { +func (n *NodeAbstractResourceInstance) readResourceInstanceState(ctx EvalContext, addr addrs.AbsResourceInstance) (*states.ResourceInstanceObject, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics - provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider) + provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) if err != nil { diags = diags.Append(err) return nil, diags @@ -526,9 +552,9 @@ func (n *NodeAbstractResource) readResourceInstanceState(ctx EvalContext, addr a // readResourceInstanceStateDeposed reads the deposed object for a specific // instance in the state. -func (n *NodeAbstractResource) readResourceInstanceStateDeposed(ctx EvalContext, addr addrs.AbsResourceInstance, key states.DeposedKey) (*states.ResourceInstanceObject, tfdiags.Diagnostics) { +func (n *NodeAbstractResourceInstance) readResourceInstanceStateDeposed(ctx EvalContext, addr addrs.AbsResourceInstance, key states.DeposedKey) (*states.ResourceInstanceObject, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics - provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider) + provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) if err != nil { diags = diags.Append(err) return nil, diags diff --git a/internal/tofu/node_resource_abstract_instance.go b/internal/tofu/node_resource_abstract_instance.go index c25d2057e1..714a0356b8 100644 --- a/internal/tofu/node_resource_abstract_instance.go +++ b/internal/tofu/node_resource_abstract_instance.go @@ -46,6 +46,8 @@ type NodeAbstractResourceInstance struct { // During import we may generate configuration for a resource, which needs // to be stored in the final change. generatedConfigHCL string + + ResolvedProviderKey addrs.InstanceKey } // NewNodeAbstractResourceInstance creates an abstract resource instance graph @@ -105,6 +107,67 @@ func (n *NodeAbstractResourceInstance) References() []*addrs.Reference { return nil } +func (n *NodeAbstractResourceInstance) resolveProvider(ctx EvalContext) tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + + log.Printf("[TRACE] Resolving provider key for %s", n.Addr) + + if n.ResolvedProvider.ProviderConfig.Provider.Type == "" { + return diags.Append(fmt.Errorf("attempting to resolve an unset provider at %s", n.Addr)) + } + + if n.ResolvedProvider.KeyExact != nil { + // Pass through from state + n.ResolvedProviderKey = n.ResolvedProvider.KeyExact + } else if n.ResolvedProvider.KeyExpression != nil { + if n.ResolvedProvider.KeyResource { + // Resolved from resource instance + n.ResolvedProviderKey, diags = resolveProviderResourceInstance(ctx, n.Config.ProviderConfigRef.KeyExpression, n.Addr) + } else { + // Resolved fro module instance + moduleInstanceForKey := n.Addr.Module[:len(n.ResolvedProvider.KeyModule)] + if !moduleInstanceForKey.Module().Equal(n.ResolvedProvider.KeyModule) { + panic(fmt.Sprintf("Invalid module key expression location %s in resource %s", n.ResolvedProvider.KeyModule, n.Addr)) + } + + n.ResolvedProviderKey, diags = resolveProviderModuleInstance(ctx, n.ResolvedProvider.KeyExpression, moduleInstanceForKey, n.Addr.String()) + } + } + + if diags.HasErrors() { + return diags + } + + log.Printf("[TRACE] Resolved provider key for %s as %s", n.Addr, n.ResolvedProviderKey) + + // This duplicates a lot of getProvider() and should be refactored as the only place to resolve the provider eventually + // This is also quite similar to ProviderTransformer's handling of removed providers for orphaned nodes + if n.ResolvedProvider.ProviderConfig.Provider.Type == "" { + // Should never happen + panic("EnsureProvider used with uninitialized provider configuration address") + } + + provider := ctx.Provider(n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) + if provider != nil { + // All good + return nil + } + + if n.ResolvedProviderKey == nil { + // Probably an OpenTofu bug + return diags.Append(fmt.Errorf("provider %s not initialized for resource %s", n.ResolvedProvider.ProviderConfig.InstanceString(n.ResolvedProviderKey), n.Addr)) + } + + return diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Provider instance not present", + fmt.Sprintf( + "To work with %s its original provider instance at %s is required, but it has been removed. This occurs when an element is removed from the provider configuration's for_each collection while objects created by that the associated provider instance still exist in the state. Re-add the for_each element to destroy %s, after which you can remove the provider configuration again.\n\nThis is commonly caused by using the same for_each collection both for a resource (or its containing module) and its associated provider configuration. To successfully remove an instance of a resource it must be possible to remove the corresponding element from the resource's for_each collection while retaining the corresponding element in the provider's for_each collection.", + n.Addr, n.ResolvedProvider.ProviderConfig.InstanceString(n.ResolvedProviderKey), n.Addr, + ), + )) +} + // StateDependencies returns the dependencies which will be saved in the state // for managed resources, or the most current dependencies for data resources. func (n *NodeAbstractResourceInstance) StateDependencies() []addrs.ConfigResource { @@ -135,7 +198,10 @@ func (n *NodeAbstractResourceInstance) AttachResourceState(s *states.Resource) { } log.Printf("[TRACE] NodeAbstractResourceInstance.AttachResourceState for %s", n.Addr) n.instanceState = s.Instance(n.Addr.Resource.Key) - n.storedProviderConfig = s.ProviderConfig + n.storedProviderConfig = ResolvedProvider{ + ProviderConfig: s.ProviderConfig, + KeyExact: n.instanceState.ProviderKey, + } } // readDiff returns the planned change for a particular resource instance @@ -273,7 +339,7 @@ func (n *NodeAbstractResourceInstance) writeResourceInstanceStateDeposed(ctx Eva // objects you are intending to write. func (n *NodeAbstractResourceInstance) writeResourceInstanceStateImpl(ctx EvalContext, deposedKey states.DeposedKey, obj *states.ResourceInstanceObject, targetState phaseState) error { absAddr := n.Addr - _, providerSchema, err := n.getProvider(ctx, n.ResolvedProvider) + _, providerSchema, err := n.getProvider(ctx) if err != nil { return err } @@ -311,11 +377,11 @@ func (n *NodeAbstractResourceInstance) writeResourceInstanceStateImpl(ctx EvalCo var write func(src *states.ResourceInstanceObjectSrc) if deposedKey == states.NotDeposed { write = func(src *states.ResourceInstanceObjectSrc) { - state.SetResourceInstanceCurrent(absAddr, src, n.ResolvedProvider) + state.SetResourceInstanceCurrent(absAddr, src, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) } } else { write = func(src *states.ResourceInstanceObjectSrc) { - state.SetResourceInstanceDeposed(absAddr, deposedKey, src, n.ResolvedProvider) + state.SetResourceInstanceDeposed(absAddr, deposedKey, src, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) } } @@ -364,7 +430,7 @@ func (n *NodeAbstractResourceInstance) planForget(ctx EvalContext, currentState Before: currentState.Value, After: nullVal, }, - ProviderAddr: n.ResolvedProvider, + ProviderAddr: n.ResolvedProvider.ProviderConfig, } return plan @@ -377,7 +443,7 @@ func (n *NodeAbstractResourceInstance) planDestroy(ctx EvalContext, currentState absAddr := n.Addr - if n.ResolvedProvider.Provider.Type == "" { + if n.ResolvedProvider.ProviderConfig.Provider.Type == "" { if deposedKey == "" { panic(fmt.Sprintf("planDestroy for %s does not have ProviderAddr set", absAddr)) } else { @@ -401,7 +467,7 @@ func (n *NodeAbstractResourceInstance) planDestroy(ctx EvalContext, currentState Before: cty.NullVal(cty.DynamicPseudoType), After: cty.NullVal(cty.DynamicPseudoType), }, - ProviderAddr: n.ResolvedProvider, + ProviderAddr: n.ResolvedProvider.ProviderConfig, } return noop, nil } @@ -412,7 +478,7 @@ func (n *NodeAbstractResourceInstance) planDestroy(ctx EvalContext, currentState // operation. nullVal := cty.NullVal(unmarkedPriorVal.Type()) - provider, _, err := n.getProvider(ctx, n.ResolvedProvider) + provider, _, err := n.getProvider(ctx) if err != nil { return plan, diags.Append(err) } @@ -452,7 +518,7 @@ func (n *NodeAbstractResourceInstance) planDestroy(ctx EvalContext, currentState "Provider produced invalid plan", fmt.Sprintf( "Provider %q planned a non-null destroy value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", - n.ResolvedProvider.Provider, n.Addr), + n.ResolvedProvider.ProviderConfig, n.Addr), ), ) return plan, diags @@ -469,7 +535,7 @@ func (n *NodeAbstractResourceInstance) planDestroy(ctx EvalContext, currentState After: nullVal, }, Private: resp.PlannedPrivate, - ProviderAddr: n.ResolvedProvider, + ProviderAddr: n.ResolvedProvider.ProviderConfig, } return plan, diags @@ -491,7 +557,7 @@ func (n *NodeAbstractResourceInstance) writeChange(ctx EvalContext, change *plan return nil } - _, providerSchema, err := n.getProvider(ctx, n.ResolvedProvider) + _, providerSchema, err := n.getProvider(ctx) if err != nil { return err } @@ -542,7 +608,7 @@ func (n *NodeAbstractResourceInstance) refresh(ctx EvalContext, deposedKey state } else { log.Printf("[TRACE] NodeAbstractResourceInstance.refresh for %s (deposed object %s)", absAddr, deposedKey) } - provider, providerSchema, err := n.getProvider(ctx, n.ResolvedProvider) + provider, providerSchema, err := n.getProvider(ctx) if err != nil { return state, diags.Append(err) } @@ -617,7 +683,7 @@ func (n *NodeAbstractResourceInstance) refresh(ctx EvalContext, deposedKey state "Provider produced invalid object", fmt.Sprintf( "Provider %q planned an invalid value for %s during refresh: %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", - n.ResolvedProvider.Provider.String(), absAddr, tfdiags.FormatError(err), + n.ResolvedProvider.ProviderConfig.String(), absAddr, tfdiags.FormatError(err), ), )) } @@ -630,7 +696,7 @@ func (n *NodeAbstractResourceInstance) refresh(ctx EvalContext, deposedKey state // We had to fix up this object in some way, and we still need to // accept any changes for compatibility, so all we can do is log a // warning about the change. - log.Printf("[WARN] Provider %q produced an invalid new value containing null blocks for %q during refresh\n", n.ResolvedProvider.Provider, n.Addr) + log.Printf("[WARN] Provider %q produced an invalid new value containing null blocks for %q during refresh\n", n.ResolvedProvider.ProviderConfig.Provider, n.Addr) } ret := state.DeepCopy() @@ -643,7 +709,7 @@ func (n *NodeAbstractResourceInstance) refresh(ctx EvalContext, deposedKey state // external changes which will be handled by the subsequent plan. if errs := objchange.AssertObjectCompatible(schema, priorVal, ret.Value); len(errs) > 0 { var buf strings.Builder - fmt.Fprintf(&buf, "[WARN] Provider %q produced an unexpected new value for %s during refresh.", n.ResolvedProvider.Provider.String(), absAddr) + fmt.Fprintf(&buf, "[WARN] Provider %q produced an unexpected new value for %s during refresh.", n.ResolvedProvider.ProviderConfig.Provider.String(), absAddr) for _, err := range errs { fmt.Fprintf(&buf, "\n - %s", tfdiags.FormatError(err)) } @@ -682,7 +748,7 @@ func (n *NodeAbstractResourceInstance) plan( var keyData instances.RepetitionData resource := n.Addr.Resource.Resource - provider, providerSchema, err := n.getProvider(ctx, n.ResolvedProvider) + provider, providerSchema, err := n.getProvider(ctx) if err != nil { return nil, nil, keyData, diags.Append(err) } @@ -863,7 +929,7 @@ func (n *NodeAbstractResourceInstance) plan( "Provider produced invalid plan", fmt.Sprintf( "Provider %q planned an invalid value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", - n.ResolvedProvider.Provider, tfdiags.FormatErrorPrefixed(err, n.Addr.String()), + n.ResolvedProvider.ProviderConfig, tfdiags.FormatErrorPrefixed(err, n.Addr.String()), ), )) } @@ -881,7 +947,7 @@ func (n *NodeAbstractResourceInstance) plan( var buf strings.Builder fmt.Fprintf(&buf, "[WARN] Provider %q produced an invalid plan for %s, but we are tolerating it because it is using the legacy plugin SDK.\n The following problems may be the cause of any confusing errors from downstream operations:", - n.ResolvedProvider.Provider, n.Addr, + n.ResolvedProvider.ProviderConfig, n.Addr, ) for _, err := range errs { fmt.Fprintf(&buf, "\n - %s", tfdiags.FormatError(err)) @@ -894,7 +960,7 @@ func (n *NodeAbstractResourceInstance) plan( "Provider produced invalid plan", fmt.Sprintf( "Provider %q planned an invalid value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", - n.ResolvedProvider.Provider, tfdiags.FormatErrorPrefixed(err, n.Addr.String()), + n.ResolvedProvider.ProviderConfig, tfdiags.FormatErrorPrefixed(err, n.Addr.String()), ), )) } @@ -958,7 +1024,7 @@ func (n *NodeAbstractResourceInstance) plan( "Provider produced invalid plan", fmt.Sprintf( "Provider %q has indicated \"requires replacement\" on %s for a non-existent attribute path %#v.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", - n.ResolvedProvider.Provider, n.Addr, path, + n.ResolvedProvider.ProviderConfig, n.Addr, path, ), )) continue @@ -1098,7 +1164,7 @@ func (n *NodeAbstractResourceInstance) plan( "Provider produced invalid plan", fmt.Sprintf( "Provider %q planned an invalid value for %s%s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", - n.ResolvedProvider.Provider, n.Addr, tfdiags.FormatError(err), + n.ResolvedProvider.ProviderConfig, n.Addr, tfdiags.FormatError(err), ), )) } @@ -1161,7 +1227,7 @@ func (n *NodeAbstractResourceInstance) plan( Addr: n.Addr, PrevRunAddr: n.prevRunAddr(ctx), Private: plannedPrivate, - ProviderAddr: n.ResolvedProvider, + ProviderAddr: n.ResolvedProvider.ProviderConfig, Change: plans.Change{ Action: action, Before: priorVal, @@ -1442,7 +1508,7 @@ func (n *NodeAbstractResourceInstance) readDataSource(ctx EvalContext, configVal config := *n.Config - provider, providerSchema, err := n.getProvider(ctx, n.ResolvedProvider) + provider, providerSchema, err := n.getProvider(ctx) diags = diags.Append(err) if diags.HasErrors() { return newVal, diags @@ -1450,7 +1516,7 @@ func (n *NodeAbstractResourceInstance) readDataSource(ctx EvalContext, configVal schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource().Resource) if schema == nil { // Should be caught during validation, so we don't bother with a pretty error here - diags = diags.Append(fmt.Errorf("provider %q does not support data source %q", n.ResolvedProvider, n.Addr.ContainingResource().Resource.Type)) + diags = diags.Append(fmt.Errorf("provider %q does not support data source %q", n.ResolvedProvider.ProviderConfig, n.Addr.ContainingResource().Resource.Type)) return newVal, diags } @@ -1516,7 +1582,7 @@ func (n *NodeAbstractResourceInstance) readDataSource(ctx EvalContext, configVal "Provider produced invalid object", fmt.Sprintf( "Provider %q produced an invalid value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", - n.ResolvedProvider, tfdiags.FormatErrorPrefixed(err, n.Addr.String()), + n.ResolvedProvider.ProviderConfig, tfdiags.FormatErrorPrefixed(err, n.Addr.String()), ), )) } @@ -1530,7 +1596,7 @@ func (n *NodeAbstractResourceInstance) readDataSource(ctx EvalContext, configVal "Provider produced null object", fmt.Sprintf( "Provider %q produced a null value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", - n.ResolvedProvider, n.Addr, + n.ResolvedProvider.ProviderConfig, n.Addr, ), )) } @@ -1541,7 +1607,7 @@ func (n *NodeAbstractResourceInstance) readDataSource(ctx EvalContext, configVal "Provider produced invalid object", fmt.Sprintf( "Provider %q produced a value for %s that is not wholly known.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", - n.ResolvedProvider, n.Addr, + n.ResolvedProvider.ProviderConfig, n.Addr, ), )) @@ -1569,17 +1635,17 @@ func (n *NodeAbstractResourceInstance) providerMetas(ctx EvalContext) (cty.Value var diags tfdiags.Diagnostics metaConfigVal := cty.NullVal(cty.DynamicPseudoType) - _, providerSchema, err := n.getProvider(ctx, n.ResolvedProvider) + _, providerSchema, err := n.getProvider(ctx) if err != nil { return metaConfigVal, diags.Append(err) } if n.ProviderMetas != nil { - if m, ok := n.ProviderMetas[n.ResolvedProvider.Provider]; ok && m != nil { + if m, ok := n.ProviderMetas[n.ResolvedProvider.ProviderConfig.Provider]; ok && m != nil { // if the provider doesn't support this feature, throw an error if providerSchema.ProviderMeta.Block == nil { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, - Summary: fmt.Sprintf("Provider %s doesn't support provider_meta", n.ResolvedProvider.Provider.String()), + Summary: fmt.Sprintf("Provider %s doesn't support provider_meta", n.ResolvedProvider.ProviderConfig.Provider.String()), Detail: fmt.Sprintf("The resource %s belongs to a provider that doesn't support provider_meta blocks", n.Addr.Resource), Subject: &m.ProviderRange, }) @@ -1607,7 +1673,7 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, checkRule var keyData instances.RepetitionData var configVal cty.Value - _, providerSchema, err := n.getProvider(ctx, n.ResolvedProvider) + _, providerSchema, err := n.getProvider(ctx) if err != nil { return nil, nil, keyData, diags.Append(err) } @@ -1616,7 +1682,7 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, checkRule schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource().Resource) if schema == nil { // Should be caught during validation, so we don't bother with a pretty error here - diags = diags.Append(fmt.Errorf("provider %q does not support data source %q", n.ResolvedProvider, n.Addr.ContainingResource().Resource.Type)) + diags = diags.Append(fmt.Errorf("provider %q does not support data source %q", n.ResolvedProvider.ProviderConfig, n.Addr.ContainingResource().Resource.Type)) return nil, nil, keyData, diags } @@ -1710,7 +1776,7 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, checkRule plannedChange := &plans.ResourceInstanceChange{ Addr: n.Addr, PrevRunAddr: n.prevRunAddr(ctx), - ProviderAddr: n.ResolvedProvider, + ProviderAddr: n.ResolvedProvider.ProviderConfig, Change: plans.Change{ Action: plans.Read, Before: priorVal, @@ -1786,7 +1852,7 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, checkRule plannedChange = &plans.ResourceInstanceChange{ Addr: n.Addr, PrevRunAddr: n.prevRunAddr(ctx), - ProviderAddr: n.ResolvedProvider, + ProviderAddr: n.ResolvedProvider.ProviderConfig, Change: plans.Change{ Action: plans.Read, Before: priorVal, @@ -1881,7 +1947,7 @@ func (n *NodeAbstractResourceInstance) applyDataSource(ctx EvalContext, planned var diags tfdiags.Diagnostics var keyData instances.RepetitionData - _, providerSchema, err := n.getProvider(ctx, n.ResolvedProvider) + _, providerSchema, err := n.getProvider(ctx) if err != nil { return nil, keyData, diags.Append(err) } @@ -1899,7 +1965,7 @@ func (n *NodeAbstractResourceInstance) applyDataSource(ctx EvalContext, planned schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource().Resource) if schema == nil { // Should be caught during validation, so we don't bother with a pretty error here - diags = diags.Append(fmt.Errorf("provider %q does not support data source %q", n.ResolvedProvider, n.Addr.ContainingResource().Resource.Type)) + diags = diags.Append(fmt.Errorf("provider %q does not support data source %q", n.ResolvedProvider.ProviderConfig, n.Addr.ContainingResource().Resource.Type)) return nil, keyData, diags } @@ -2259,7 +2325,7 @@ func (n *NodeAbstractResourceInstance) apply( return state, diags } - provider, providerSchema, err := n.getProvider(ctx, n.ResolvedProvider) + provider, providerSchema, err := n.getProvider(ctx) if err != nil { return nil, diags.Append(err) } @@ -2387,7 +2453,7 @@ func (n *NodeAbstractResourceInstance) apply( "Provider produced invalid object", fmt.Sprintf( "Provider %q produced an invalid nil value after apply for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", - n.ResolvedProvider.String(), n.Addr.String(), + n.ResolvedProvider.ProviderConfig.String(), n.Addr.String(), ), )) } @@ -2400,7 +2466,7 @@ func (n *NodeAbstractResourceInstance) apply( "Provider produced invalid object", fmt.Sprintf( "Provider %q produced an invalid value after apply for %s. The result cannot not be saved in the OpenTofu state.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", - n.ResolvedProvider.String(), tfdiags.FormatErrorPrefixed(err, n.Addr.String()), + n.ResolvedProvider.ProviderConfig.String(), tfdiags.FormatErrorPrefixed(err, n.Addr.String()), ), )) } @@ -2470,7 +2536,7 @@ func (n *NodeAbstractResourceInstance) apply( // to notice in the logs if an inconsistency beyond the type system // leads to a downstream provider failure. var buf strings.Builder - fmt.Fprintf(&buf, "[WARN] Provider %q produced an unexpected new value for %s, but we are tolerating it because it is using the legacy plugin SDK.\n The following problems may be the cause of any confusing errors from downstream operations:", n.ResolvedProvider.String(), n.Addr) + fmt.Fprintf(&buf, "[WARN] Provider %q produced an unexpected new value for %s, but we are tolerating it because it is using the legacy plugin SDK.\n The following problems may be the cause of any confusing errors from downstream operations:", n.ResolvedProvider.ProviderConfig.String(), n.Addr) for _, err := range errs { fmt.Fprintf(&buf, "\n - %s", tfdiags.FormatError(err)) } @@ -2490,7 +2556,7 @@ func (n *NodeAbstractResourceInstance) apply( "Provider produced inconsistent result after apply", fmt.Sprintf( "When applying changes to %s, provider %q produced an unexpected new value: %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", - n.Addr, n.ResolvedProvider.String(), tfdiags.FormatError(err), + n.Addr, n.ResolvedProvider.ProviderConfig.String(), tfdiags.FormatError(err), ), )) } @@ -2579,8 +2645,8 @@ func resourceInstancePrevRunAddr(ctx EvalContext, currentAddr addrs.AbsResourceI return table.OldAddr(currentAddr) } -func (n *NodeAbstractResourceInstance) getProvider(ctx EvalContext, addr addrs.AbsProviderConfig) (providers.Interface, providers.ProviderSchema, error) { - underlyingProvider, schema, err := getProvider(ctx, addr) +func (n *NodeAbstractResourceInstance) getProvider(ctx EvalContext) (providers.Interface, providers.ProviderSchema, error) { + underlyingProvider, schema, err := getProvider(ctx, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) if err != nil { return nil, providers.ProviderSchema{}, err } diff --git a/internal/tofu/node_resource_abstract_instance_test.go b/internal/tofu/node_resource_abstract_instance_test.go index cb46ffed9c..a142c5a966 100644 --- a/internal/tofu/node_resource_abstract_instance_test.go +++ b/internal/tofu/node_resource_abstract_instance_test.go @@ -131,9 +131,11 @@ func TestNodeAbstractResourceInstanceProvider(t *testing.T) { // function. (This would not be valid for some other functions.) Addr: test.Addr, NodeAbstractResource: NodeAbstractResource{ - Addr: test.Addr.ConfigResource(), - Config: test.Config, - storedProviderConfig: test.StoredProviderConfig, + Addr: test.Addr.ConfigResource(), + Config: test.Config, + storedProviderConfig: ResolvedProvider{ + ProviderConfig: test.StoredProviderConfig, + }, }, } got := node.Provider() @@ -170,7 +172,7 @@ func TestNodeAbstractResourceInstance_WriteResourceInstanceState(t *testing.T) { Addr: mustResourceInstanceAddr("aws_instance.foo"), // instanceState: obj, NodeAbstractResource: NodeAbstractResource{ - ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + ResolvedProvider: ResolvedProvider{ProviderConfig: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`)}, }, } ctx.ProviderProvider = mockProvider diff --git a/internal/tofu/node_resource_abstract_test.go b/internal/tofu/node_resource_abstract_test.go index 22252f798d..6e1d53184a 100644 --- a/internal/tofu/node_resource_abstract_test.go +++ b/internal/tofu/node_resource_abstract_test.go @@ -145,7 +145,7 @@ func TestNodeAbstractResourceSetProvider(t *testing.T) { p := node.ProvidedBy() // the implied non-exact provider should be "terraform" - lpc, ok := p.(addrs.LocalProviderConfig) + lpc, ok := p.ProviderConfig.(addrs.LocalProviderConfig) if !ok { t.Fatalf("expected LocalProviderConfig, got %#v\n", p) } @@ -165,10 +165,10 @@ func TestNodeAbstractResourceSetProvider(t *testing.T) { Alias: "test", } - node.SetProvider(resolved) + node.SetProvider(ResolvedProvider{ProviderConfig: resolved}) p = node.ProvidedBy() - apc, ok := p.(addrs.AbsProviderConfig) + apc, ok := p.ProviderConfig.(addrs.AbsProviderConfig) if !ok { t.Fatalf("expected AbsProviderConfig, got %#v\n", p) } @@ -193,7 +193,7 @@ func TestNodeAbstractResource_ReadResourceInstanceState(t *testing.T) { tests := map[string]struct { State *states.State - Node *NodeAbstractResource + Node *NodeAbstractResourceInstance ExpectedInstanceId string }{ "ReadState gets primary instance state": { @@ -211,12 +211,12 @@ func TestNodeAbstractResource_ReadResourceInstanceState(t *testing.T) { s.SetResourceInstanceCurrent(oneAddr.Instance(addrs.NoKey), &states.ResourceInstanceObjectSrc{ Status: states.ObjectReady, AttrsJSON: []byte(`{"id":"i-abc123"}`), - }, providerAddr) + }, providerAddr, addrs.NoKey) }), - Node: &NodeAbstractResource{ + Node: &NodeAbstractResourceInstance{NodeAbstractResource: NodeAbstractResource{ Addr: mustConfigResourceAddr("aws_instance.bar"), - ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), - }, + ResolvedProvider: ResolvedProvider{ProviderConfig: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`)}, + }}, ExpectedInstanceId: "i-abc123", }, } @@ -230,7 +230,7 @@ func TestNodeAbstractResource_ReadResourceInstanceState(t *testing.T) { ctx.ProviderProvider = providers.Interface(mockProvider) - got, readDiags := test.Node.readResourceInstanceState(ctx, test.Node.Addr.Resource.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)) + got, readDiags := test.Node.readResourceInstanceState(ctx, test.Node.NodeAbstractResource.Addr.Resource.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)) if readDiags.HasErrors() { t.Fatalf("[%s] Got err: %#v", k, readDiags.Err()) } @@ -259,7 +259,7 @@ func TestNodeAbstractResource_ReadResourceInstanceStateDeposed(t *testing.T) { tests := map[string]struct { State *states.State - Node *NodeAbstractResource + Node *NodeAbstractResourceInstance ExpectedInstanceId string }{ "ReadStateDeposed gets deposed instance": { @@ -277,12 +277,12 @@ func TestNodeAbstractResource_ReadResourceInstanceStateDeposed(t *testing.T) { s.SetResourceInstanceDeposed(oneAddr.Instance(addrs.NoKey), states.DeposedKey("00000001"), &states.ResourceInstanceObjectSrc{ Status: states.ObjectReady, AttrsJSON: []byte(`{"id":"i-abc123"}`), - }, providerAddr) + }, providerAddr, addrs.NoKey) }), - Node: &NodeAbstractResource{ + Node: &NodeAbstractResourceInstance{NodeAbstractResource: NodeAbstractResource{ Addr: mustConfigResourceAddr("aws_instance.bar"), - ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), - }, + ResolvedProvider: ResolvedProvider{ProviderConfig: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`)}, + }}, ExpectedInstanceId: "i-abc123", }, } @@ -296,7 +296,7 @@ func TestNodeAbstractResource_ReadResourceInstanceStateDeposed(t *testing.T) { key := states.DeposedKey("00000001") // shim from legacy state assigns 0th deposed index this key - got, readDiags := test.Node.readResourceInstanceStateDeposed(ctx, test.Node.Addr.Resource.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), key) + got, readDiags := test.Node.readResourceInstanceStateDeposed(ctx, test.Node.NodeAbstractResource.Addr.Resource.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), key) if readDiags.HasErrors() { t.Fatalf("[%s] Got err: %#v", k, readDiags.Err()) } diff --git a/internal/tofu/node_resource_apply_instance.go b/internal/tofu/node_resource_apply_instance.go index c187e8f969..a66f3e1930 100644 --- a/internal/tofu/node_resource_apply_instance.go +++ b/internal/tofu/node_resource_apply_instance.go @@ -141,6 +141,11 @@ func (n *NodeApplyableResourceInstance) Execute(ctx EvalContext, op walkOperatio return diags } + diags = n.resolveProvider(ctx) + if diags.HasErrors() { + return diags + } + // Eval info is different depending on what kind of resource this is switch n.Config.Mode { case addrs.ManagedResourceMode: @@ -153,7 +158,7 @@ func (n *NodeApplyableResourceInstance) Execute(ctx EvalContext, op walkOperatio } func (n *NodeApplyableResourceInstance) dataResourceExecute(ctx EvalContext) (diags tfdiags.Diagnostics) { - _, providerSchema, err := getProvider(ctx, n.ResolvedProvider) + _, providerSchema, err := getProvider(ctx, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) diags = diags.Append(err) if diags.HasErrors() { return diags @@ -221,7 +226,7 @@ func (n *NodeApplyableResourceInstance) managedResourceExecute(ctx EvalContext) var deposedKey states.DeposedKey addr := n.ResourceInstanceAddr().Resource - _, providerSchema, err := getProvider(ctx, n.ResolvedProvider) + _, providerSchema, err := getProvider(ctx, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) diags = diags.Append(err) if diags.HasErrors() { return diags @@ -443,7 +448,7 @@ func (n *NodeApplyableResourceInstance) checkPlannedChange(ctx EvalContext, plan "Provider produced inconsistent final plan", fmt.Sprintf( "When expanding the plan for %s to include new values learned so far during apply, provider %q changed the planned action from %s to %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", - absAddr, n.ResolvedProvider.Provider.String(), + absAddr, n.ResolvedProvider.ProviderConfig.Provider.String(), plannedChange.Action, actualChange.Action, ), )) @@ -457,7 +462,7 @@ func (n *NodeApplyableResourceInstance) checkPlannedChange(ctx EvalContext, plan "Provider produced inconsistent final plan", fmt.Sprintf( "When expanding the plan for %s to include new values learned so far during apply, provider %q produced an invalid new value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", - absAddr, n.ResolvedProvider.Provider.String(), tfdiags.FormatError(err), + absAddr, n.ResolvedProvider.ProviderConfig.Provider.String(), tfdiags.FormatError(err), ), )) } diff --git a/internal/tofu/node_resource_apply_test.go b/internal/tofu/node_resource_apply_test.go index 9432635b19..816c6ed106 100644 --- a/internal/tofu/node_resource_apply_test.go +++ b/internal/tofu/node_resource_apply_test.go @@ -53,10 +53,10 @@ func TestNodeExpandApplyableResourceExecute(t *testing.T) { Type: "test_instance", Name: "foo", }, - ResolvedProvider: addrs.AbsProviderConfig{ + ResolvedProvider: ResolvedProvider{ProviderConfig: addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, - }, + }}, }, } diags := node.Execute(ctx, walkApply) diff --git a/internal/tofu/node_resource_deposed.go b/internal/tofu/node_resource_deposed.go index 91e09c9b42..327489ebce 100644 --- a/internal/tofu/node_resource_deposed.go +++ b/internal/tofu/node_resource_deposed.go @@ -328,12 +328,12 @@ func (n *NodeDestroyDeposedResourceInstanceObject) writeResourceInstanceState(ct if obj == nil { // No need to encode anything: we'll just write it directly. - state.SetResourceInstanceDeposed(absAddr, key, nil, n.ResolvedProvider) + state.SetResourceInstanceDeposed(absAddr, key, nil, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) log.Printf("[TRACE] writeResourceInstanceStateDeposed: removing state object for %s deposed %s", absAddr, key) return nil } - _, providerSchema, err := getProvider(ctx, n.ResolvedProvider) + _, providerSchema, err := getProvider(ctx, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) if err != nil { return err } @@ -351,7 +351,7 @@ func (n *NodeDestroyDeposedResourceInstanceObject) writeResourceInstanceState(ct } log.Printf("[TRACE] writeResourceInstanceStateDeposed: writing state object for %s deposed %s", absAddr, key) - state.SetResourceInstanceDeposed(absAddr, key, src, n.ResolvedProvider) + state.SetResourceInstanceDeposed(absAddr, key, src, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) return nil } diff --git a/internal/tofu/node_resource_deposed_test.go b/internal/tofu/node_resource_deposed_test.go index 6eff88f5e4..2b0623a63c 100644 --- a/internal/tofu/node_resource_deposed_test.go +++ b/internal/tofu/node_resource_deposed_test.go @@ -96,7 +96,7 @@ func TestNodePlanDeposedResourceInstanceObject_Execute(t *testing.T) { NodeAbstractResourceInstance: &NodeAbstractResourceInstance{ Addr: absResource, NodeAbstractResource: NodeAbstractResource{ - ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + ResolvedProvider: ResolvedProvider{ProviderConfig: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)}, }, }, DeposedKey: deposedKey, @@ -133,7 +133,7 @@ func TestNodeDestroyDeposedResourceInstanceObject_Execute(t *testing.T) { NodeAbstractResourceInstance: &NodeAbstractResourceInstance{ Addr: absResource, NodeAbstractResource: NodeAbstractResource{ - ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + ResolvedProvider: ResolvedProvider{ProviderConfig: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)}, }, }, DeposedKey: deposedKey, @@ -174,7 +174,7 @@ func TestNodeDestroyDeposedResourceInstanceObject_WriteResourceInstanceState(t * node := &NodeDestroyDeposedResourceInstanceObject{ NodeAbstractResourceInstance: &NodeAbstractResourceInstance{ NodeAbstractResource: NodeAbstractResource{ - ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + ResolvedProvider: ResolvedProvider{ProviderConfig: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`)}, }, Addr: mustResourceInstanceAddr("aws_instance.foo"), }, @@ -206,7 +206,7 @@ func TestNodeDestroyDeposedResourceInstanceObject_ExecuteMissingState(t *testing NodeAbstractResourceInstance: &NodeAbstractResourceInstance{ Addr: mustResourceInstanceAddr("test_object.foo"), NodeAbstractResource: NodeAbstractResource{ - ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + ResolvedProvider: ResolvedProvider{ProviderConfig: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)}, }, }, DeposedKey: states.NewDeposedKey(), @@ -229,7 +229,7 @@ func TestNodeForgetDeposedResourceInstanceObject_Execute(t *testing.T) { NodeAbstractResourceInstance: &NodeAbstractResourceInstance{ Addr: absResource, NodeAbstractResource: NodeAbstractResource{ - ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + ResolvedProvider: ResolvedProvider{ProviderConfig: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)}, }, }, DeposedKey: deposedKey, @@ -261,6 +261,7 @@ func initMockEvalContext(resourceAddrs string, deposedKey states.DeposedKey) (*M AttrsJSON: []byte(`{"id":"bar"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) schema := providers.ProviderSchema{ diff --git a/internal/tofu/node_resource_destroy.go b/internal/tofu/node_resource_destroy.go index 1fd83d5dd7..6646430ae2 100644 --- a/internal/tofu/node_resource_destroy.go +++ b/internal/tofu/node_resource_destroy.go @@ -49,10 +49,10 @@ func (n *NodeDestroyResourceInstance) Name() string { return n.ResourceInstanceAddr().String() + " (destroy)" } -func (n *NodeDestroyResourceInstance) ProvidedBy() addrs.ProviderConfig { +func (n *NodeDestroyResourceInstance) ProvidedBy() RequestedProvider { if n.Addr.Resource.Resource.Mode == addrs.DataResourceMode { // indicate that this node does not require a configured provider - return nil + return RequestedProvider{} } return n.NodeAbstractResourceInstance.ProvidedBy() } @@ -143,6 +143,10 @@ func (n *NodeDestroyResourceInstance) Execute(ctx EvalContext, op walkOperation) // Eval info is different depending on what kind of resource this is switch addr.Resource.Resource.Mode { case addrs.ManagedResourceMode: + diags = n.resolveProvider(ctx) + if diags.HasErrors() { + return diags + } return n.managedResourceExecute(ctx) case addrs.DataResourceMode: return n.dataResourceExecute(ctx) @@ -164,7 +168,7 @@ func (n *NodeDestroyResourceInstance) managedResourceExecute(ctx EvalContext) (d var changeApply *plans.ResourceInstanceChange var state *states.ResourceInstanceObject - _, providerSchema, err := getProvider(ctx, n.ResolvedProvider) + _, providerSchema, err := getProvider(ctx, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) diags = diags.Append(err) if diags.HasErrors() { return diags @@ -234,6 +238,6 @@ func (n *NodeDestroyResourceInstance) managedResourceExecute(ctx EvalContext) (d func (n *NodeDestroyResourceInstance) dataResourceExecute(ctx EvalContext) (diags tfdiags.Diagnostics) { log.Printf("[TRACE] NodeDestroyResourceInstance: removing state object for %s", n.Addr) - ctx.State().SetResourceInstanceCurrent(n.Addr, nil, n.ResolvedProvider) + ctx.State().SetResourceInstanceCurrent(n.Addr, nil, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) return diags.Append(updateStateHook(ctx)) } diff --git a/internal/tofu/node_resource_import.go b/internal/tofu/node_resource_import.go index a6ffdaaa98..941560ce17 100644 --- a/internal/tofu/node_resource_import.go +++ b/internal/tofu/node_resource_import.go @@ -18,9 +18,10 @@ import ( ) type graphNodeImportState struct { - Addr addrs.AbsResourceInstance // Addr is the resource address to import into - ID string // ID is the ID to import as - ResolvedProvider addrs.AbsProviderConfig // provider node address after resolution + Addr addrs.AbsResourceInstance // Addr is the resource address to import into + ID string // ID is the ID to import as + ResolvedProvider ResolvedProvider // provider node address after resolution + ResolvedProviderKey addrs.InstanceKey // resolved from ResolvedProviderKeyExpr+ResolvedProviderKeyPath in method Execute Schema *configschema.Block // Schema for processing the configuration body SchemaVersion uint64 // Schema version of "Schema", as decided by the provider @@ -41,20 +42,26 @@ func (n *graphNodeImportState) Name() string { } // GraphNodeProviderConsumer -func (n *graphNodeImportState) ProvidedBy() addrs.ProviderConfig { +func (n *graphNodeImportState) ProvidedBy() RequestedProvider { // This has already been resolved by nodeExpandPlannableResource - return n.ResolvedProvider + return RequestedProvider{ + ProviderConfig: n.ResolvedProvider.ProviderConfig, + KeyExpression: n.ResolvedProvider.KeyExpression, + KeyModule: n.ResolvedProvider.KeyModule, + KeyResource: n.ResolvedProvider.KeyResource, + KeyExact: n.ResolvedProvider.KeyExact, + } } // GraphNodeProviderConsumer func (n *graphNodeImportState) Provider() addrs.Provider { // This has already been resolved by nodeExpandPlannableResource - return n.ResolvedProvider.Provider + return n.ResolvedProvider.ProviderConfig.Provider } // GraphNodeProviderConsumer -func (n *graphNodeImportState) SetProvider(addr addrs.AbsProviderConfig) { - n.ResolvedProvider = addr +func (n *graphNodeImportState) SetProvider(resolved ResolvedProvider) { + n.ResolvedProvider = resolved } // GraphNodeModuleInstance @@ -72,7 +79,27 @@ func (n *graphNodeImportState) Execute(ctx EvalContext, op walkOperation) (diags // Reset our states n.states = nil - provider, _, err := getProvider(ctx, n.ResolvedProvider) + // FIXME, yuck: borrowing some logic that's currently only available for the abstract resource instance + // node, even though graphNodeImportState doesn't actually embed that type for some reason. + // Let's factor this logic out somewhere that's explicitly shareable. + asAbsNode := &NodeAbstractResourceInstance{ + Addr: n.Addr, + NodeAbstractResource: NodeAbstractResource{ + Addr: n.Addr.ConfigResource(), + Config: n.Config, + Schema: n.Schema, + SchemaVersion: n.SchemaVersion, + ResolvedProvider: n.ResolvedProvider, + }, + } + diags = diags.Append(asAbsNode.resolveProvider(ctx)) + if diags.HasErrors() { + return diags + } + n.ResolvedProviderKey = asAbsNode.ResolvedProviderKey + log.Printf("[TRACE] graphNodeImportState: importing using %s instance %s", n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) + + provider, _, err := getProvider(ctx, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) diags = diags.Append(err) if diags.HasErrors() { return diags @@ -172,12 +199,13 @@ func (n *graphNodeImportState) DynamicExpand(ctx EvalContext) (*Graph, error) { // safe. for i, state := range n.states { g.Add(&graphNodeImportStateSub{ - TargetAddr: addrs[i], - State: state, - ResolvedProvider: n.ResolvedProvider, - Schema: n.Schema, - SchemaVersion: n.SchemaVersion, - Config: n.Config, + TargetAddr: addrs[i], + State: state, + ResolvedProvider: n.ResolvedProvider, + ResolvedProviderKey: n.ResolvedProviderKey, + Schema: n.Schema, + SchemaVersion: n.SchemaVersion, + Config: n.Config, }) } @@ -191,14 +219,14 @@ func (n *graphNodeImportState) DynamicExpand(ctx EvalContext) (*Graph, error) { // and is part of the subgraph. This node is responsible for refreshing // and adding a resource to the state once it is imported. type graphNodeImportStateSub struct { - TargetAddr addrs.AbsResourceInstance - State providers.ImportedResource - ResolvedProvider addrs.AbsProviderConfig + TargetAddr addrs.AbsResourceInstance + State providers.ImportedResource + ResolvedProvider ResolvedProvider + ResolvedProviderKey addrs.InstanceKey // the dynamic instance ResolvedProvider Schema *configschema.Block // Schema for processing the configuration body SchemaVersion uint64 // Schema version of "Schema", as decided by the provider Config *configs.Resource // Config is the resource in the config - } var ( @@ -230,6 +258,7 @@ func (n *graphNodeImportStateSub) Execute(ctx EvalContext, op walkOperation) (di NodeAbstractResource: NodeAbstractResource{ ResolvedProvider: n.ResolvedProvider, }, + ResolvedProviderKey: n.ResolvedProviderKey, } state, refreshDiags := riNode.refresh(ctx, states.NotDeposed, state) diags = diags.Append(refreshDiags) diff --git a/internal/tofu/node_resource_plan.go b/internal/tofu/node_resource_plan.go index 15f0ad8c29..86aec5848f 100644 --- a/internal/tofu/node_resource_plan.go +++ b/internal/tofu/node_resource_plan.go @@ -128,6 +128,7 @@ func (n *nodeExpandPlannableResource) DynamicExpand(ctx EvalContext) (*Graph, er // Add the config and state since we don't do that via transforms a.Config = n.Config a.ResolvedProvider = n.ResolvedProvider + // ResolvedProviderKey set in AttachResourceState a.Schema = n.Schema a.ProvisionerSchemas = n.ProvisionerSchemas a.ProviderMetas = n.ProviderMetas @@ -380,6 +381,7 @@ func (n *nodeExpandPlannableResource) resourceInstanceSubgraph(ctx EvalContext, // Add the config and state since we don't do that via transforms a.Config = n.Config a.ResolvedProvider = n.ResolvedProvider + // ResolvedProviderKey will be set during AttachResourceState a.Schema = n.Schema a.ProvisionerSchemas = n.ProvisionerSchemas a.ProviderMetas = n.ProviderMetas @@ -427,6 +429,6 @@ func (n *nodeExpandPlannableResource) resourceInstanceSubgraph(ctx EvalContext, Steps: steps, Name: "nodeExpandPlannableResource", } - graph, diags := b.Build(addr.Module) - return graph, diags.ErrWithWarnings() + graph, graphDiags := b.Build(addr.Module) + return graph, diags.Append(graphDiags).ErrWithWarnings() } diff --git a/internal/tofu/node_resource_plan_destroy.go b/internal/tofu/node_resource_plan_destroy.go index 0d6a9e7078..d375d5cb58 100644 --- a/internal/tofu/node_resource_plan_destroy.go +++ b/internal/tofu/node_resource_plan_destroy.go @@ -121,7 +121,7 @@ func (n *NodePlanDestroyableResourceInstance) dataResourceExecute(ctx EvalContex Before: cty.NullVal(cty.DynamicPseudoType), After: cty.NullVal(cty.DynamicPseudoType), }, - ProviderAddr: n.ResolvedProvider, + ProviderAddr: n.ResolvedProvider.ProviderConfig, } return diags.Append(n.writeChange(ctx, change, "")) } diff --git a/internal/tofu/node_resource_plan_instance.go b/internal/tofu/node_resource_plan_instance.go index 73fedffaba..90a55932bb 100644 --- a/internal/tofu/node_resource_plan_instance.go +++ b/internal/tofu/node_resource_plan_instance.go @@ -86,6 +86,11 @@ var ( func (n *NodePlannableResourceInstance) Execute(ctx EvalContext, op walkOperation) tfdiags.Diagnostics { addr := n.ResourceInstanceAddr() + diags := n.resolveProvider(ctx) + if diags.HasErrors() { + return diags + } + // Eval info is different depending on what kind of resource this is switch addr.Resource.Resource.Mode { case addrs.ManagedResourceMode: @@ -103,7 +108,7 @@ func (n *NodePlannableResourceInstance) dataResourceExecute(ctx EvalContext) (di var change *plans.ResourceInstanceChange - _, providerSchema, err := getProvider(ctx, n.ResolvedProvider) + _, providerSchema, err := getProvider(ctx, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) diags = diags.Append(err) if diags.HasErrors() { return diags @@ -164,7 +169,7 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext) checkRuleSeverity = tfdiags.Warning } - provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider) + provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) diags = diags.Append(err) if diags.HasErrors() { return diags @@ -294,7 +299,7 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext) change := &plans.ResourceInstanceChange{ Addr: n.Addr, PrevRunAddr: n.prevRunAddr(ctx), - ProviderAddr: n.ResolvedProvider, + ProviderAddr: n.ResolvedProvider.ProviderConfig, Change: plans.Change{ // we only need a placeholder, so this will be a NoOp Action: plans.NoOp, @@ -532,6 +537,7 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs. NodeAbstractResource: NodeAbstractResource{ ResolvedProvider: n.ResolvedProvider, }, + ResolvedProviderKey: n.ResolvedProviderKey, } instanceRefreshState, refreshDiags := riNode.refresh(ctx, states.NotDeposed, importedState) diags = diags.Append(refreshDiags) @@ -632,7 +638,7 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs. Name: n.Addr.Resource.Resource.Name, Config: remain, Managed: &configs.ManagedResource{}, - Provider: n.ResolvedProvider.Provider, + Provider: n.ResolvedProvider.ProviderConfig.Provider, } } @@ -675,8 +681,8 @@ func (n *NodePlannableResourceInstance) generateHCLStringAttributes(addr addrs.A ) providerAddr := addrs.LocalProviderConfig{ - LocalName: n.ResolvedProvider.Provider.Type, - Alias: n.ResolvedProvider.Alias, + LocalName: n.ResolvedProvider.ProviderConfig.Provider.Type, + Alias: n.ResolvedProvider.ProviderConfig.Alias, } return genconfig.GenerateResourceContents(addr, filteredSchema, providerAddr, state.Value) diff --git a/internal/tofu/node_resource_plan_orphan.go b/internal/tofu/node_resource_plan_orphan.go index 99f7f3964b..2de1675fbd 100644 --- a/internal/tofu/node_resource_plan_orphan.go +++ b/internal/tofu/node_resource_plan_orphan.go @@ -57,6 +57,10 @@ func (n *NodePlannableResourceInstanceOrphan) Execute(ctx EvalContext, op walkOp // Eval info is different depending on what kind of resource this is switch addr.Resource.Resource.Mode { case addrs.ManagedResourceMode: + diags := n.resolveProvider(ctx) + if diags.HasErrors() { + return diags + } return n.managedResourceExecute(ctx) case addrs.DataResourceMode: return n.dataResourceExecute(ctx) @@ -65,10 +69,10 @@ func (n *NodePlannableResourceInstanceOrphan) Execute(ctx EvalContext, op walkOp } } -func (n *NodePlannableResourceInstanceOrphan) ProvidedBy() addrs.ProviderConfig { +func (n *NodePlannableResourceInstanceOrphan) ProvidedBy() RequestedProvider { if n.Addr.Resource.Resource.Mode == addrs.DataResourceMode { // indicate that this node does not require a configured provider - return nil + return RequestedProvider{} } return n.NodeAbstractResourceInstance.ProvidedBy() } @@ -80,10 +84,10 @@ func (n *NodePlannableResourceInstanceOrphan) dataResourceExecute(ctx EvalContex // we need to update both the refresh state to refresh the current data // source, and the working state for plan-time evaluations. refreshState := ctx.RefreshState() - refreshState.SetResourceInstanceCurrent(n.Addr, nil, n.ResolvedProvider) + refreshState.SetResourceInstanceCurrent(n.Addr, nil, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) workingState := ctx.State() - workingState.SetResourceInstanceCurrent(n.Addr, nil, n.ResolvedProvider) + workingState.SetResourceInstanceCurrent(n.Addr, nil, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey) return nil } diff --git a/internal/tofu/node_resource_plan_orphan_test.go b/internal/tofu/node_resource_plan_orphan_test.go index 47324ca034..8f81f50da3 100644 --- a/internal/tofu/node_resource_plan_orphan_test.go +++ b/internal/tofu/node_resource_plan_orphan_test.go @@ -108,6 +108,7 @@ func TestNodeResourcePlanOrphan_Execute(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) schema := providers.ProviderSchema{ @@ -142,10 +143,10 @@ func TestNodeResourcePlanOrphan_Execute(t *testing.T) { node := NodePlannableResourceInstanceOrphan{ NodeAbstractResourceInstance: &NodeAbstractResourceInstance{ NodeAbstractResource: NodeAbstractResource{ - ResolvedProvider: addrs.AbsProviderConfig{ + ResolvedProvider: ResolvedProvider{ProviderConfig: addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, - }, + }}, }, Addr: absResource, }, @@ -188,6 +189,7 @@ func TestNodeResourcePlanOrphanExecute_alreadyDeleted(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) refreshState := state.DeepCopy() prevRunState := state.DeepCopy() @@ -217,10 +219,10 @@ func TestNodeResourcePlanOrphanExecute_alreadyDeleted(t *testing.T) { node := NodePlannableResourceInstanceOrphan{ NodeAbstractResourceInstance: &NodeAbstractResourceInstance{ NodeAbstractResource: NodeAbstractResource{ - ResolvedProvider: addrs.AbsProviderConfig{ + ResolvedProvider: ResolvedProvider{ProviderConfig: addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, - }, + }}, }, Addr: mustResourceInstanceAddr("test_object.foo"), }, @@ -270,6 +272,7 @@ func TestNodeResourcePlanOrphanExecute_deposed(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) refreshState := state.DeepCopy() prevRunState := state.DeepCopy() @@ -299,10 +302,10 @@ func TestNodeResourcePlanOrphanExecute_deposed(t *testing.T) { node := NodePlannableResourceInstanceOrphan{ NodeAbstractResourceInstance: &NodeAbstractResourceInstance{ NodeAbstractResource: NodeAbstractResource{ - ResolvedProvider: addrs.AbsProviderConfig{ + ResolvedProvider: ResolvedProvider{ProviderConfig: addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, - }, + }}, }, Addr: mustResourceInstanceAddr("test_object.foo"), }, diff --git a/internal/tofu/node_resource_validate.go b/internal/tofu/node_resource_validate.go index 1a117000de..319cf8ceec 100644 --- a/internal/tofu/node_resource_validate.go +++ b/internal/tofu/node_resource_validate.go @@ -282,7 +282,7 @@ var connectionBlockSupersetSchema = &configschema.Block{ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diagnostics { var diags tfdiags.Diagnostics - provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider) + provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider.ProviderConfig, addrs.NoKey) // Provider Instance Keys are ignored during validate diags = diags.Append(err) if diags.HasErrors() { return diags diff --git a/internal/tofu/node_resource_validate_test.go b/internal/tofu/node_resource_validate_test.go index e14fec4fff..df145ab41a 100644 --- a/internal/tofu/node_resource_validate_test.go +++ b/internal/tofu/node_resource_validate_test.go @@ -189,7 +189,7 @@ func TestNodeValidatableResource_ValidateResource_managedResource(t *testing.T) NodeAbstractResource: &NodeAbstractResource{ Addr: mustConfigResourceAddr("test_foo.bar"), Config: rc, - ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + ResolvedProvider: ResolvedProvider{ProviderConfig: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`)}, }, } @@ -257,7 +257,7 @@ func TestNodeValidatableResource_ValidateResource_managedResourceCount(t *testin NodeAbstractResource: &NodeAbstractResource{ Addr: mustConfigResourceAddr("test_foo.bar"), Config: rc, - ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + ResolvedProvider: ResolvedProvider{ProviderConfig: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`)}, }, } @@ -303,7 +303,7 @@ func TestNodeValidatableResource_ValidateResource_dataSource(t *testing.T) { NodeAbstractResource: &NodeAbstractResource{ Addr: mustConfigResourceAddr("test_foo.bar"), Config: rc, - ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + ResolvedProvider: ResolvedProvider{ProviderConfig: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`)}, }, } @@ -339,7 +339,7 @@ func TestNodeValidatableResource_ValidateResource_valid(t *testing.T) { NodeAbstractResource: &NodeAbstractResource{ Addr: mustConfigResourceAddr("test_object.foo"), Config: rc, - ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + ResolvedProvider: ResolvedProvider{ProviderConfig: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`)}, }, } @@ -376,7 +376,7 @@ func TestNodeValidatableResource_ValidateResource_warningsAndErrorsPassedThrough NodeAbstractResource: &NodeAbstractResource{ Addr: mustConfigResourceAddr("test_foo.bar"), Config: rc, - ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + ResolvedProvider: ResolvedProvider{ProviderConfig: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`)}, }, } @@ -438,7 +438,7 @@ func TestNodeValidatableResource_ValidateResource_invalidDependsOn(t *testing.T) NodeAbstractResource: &NodeAbstractResource{ Addr: mustConfigResourceAddr("test_foo.bar"), Config: rc, - ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + ResolvedProvider: ResolvedProvider{ProviderConfig: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`)}, }, } @@ -522,7 +522,7 @@ func TestNodeValidatableResource_ValidateResource_invalidIgnoreChangesNonexisten NodeAbstractResource: &NodeAbstractResource{ Addr: mustConfigResourceAddr("test_foo.bar"), Config: rc, - ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + ResolvedProvider: ResolvedProvider{ProviderConfig: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`)}, }, } @@ -605,7 +605,7 @@ func TestNodeValidatableResource_ValidateResource_invalidIgnoreChangesComputed(t NodeAbstractResource: &NodeAbstractResource{ Addr: mustConfigResourceAddr("test_foo.bar"), Config: rc, - ResolvedProvider: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + ResolvedProvider: ResolvedProvider{ProviderConfig: mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`)}, }, } diff --git a/internal/tofu/opentf_test.go b/internal/tofu/opentf_test.go index dfa937399e..750b18963e 100644 --- a/internal/tofu/opentf_test.go +++ b/internal/tofu/opentf_test.go @@ -155,6 +155,7 @@ func testSetResourceInstanceCurrent(module *states.Module, resource, attrsJson, AttrsJSON: []byte(attrsJson), }, mustProviderConfig(provider), + addrs.NoKey, ) } @@ -168,6 +169,7 @@ func testSetResourceInstanceTainted(module *states.Module, resource, attrsJson, AttrsJSON: []byte(attrsJson), }, mustProviderConfig(provider), + addrs.NoKey, ) } diff --git a/internal/tofu/test_context_test.go b/internal/tofu/test_context_test.go index ddfb45fe00..4de751ce91 100644 --- a/internal/tofu/test_context_test.go +++ b/internal/tofu/test_context_test.go @@ -65,7 +65,7 @@ run "test_case" { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) }), provider: &MockProvider{ GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ @@ -123,7 +123,7 @@ run "test_case" { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) }), provider: &MockProvider{ GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ @@ -183,7 +183,7 @@ run "test_case" { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) }), variables: InputValues{ "value": { @@ -240,7 +240,7 @@ run "test_case" { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) }), provider: &MockProvider{ GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ @@ -303,7 +303,7 @@ run "test_case" { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) }), provider: &MockProvider{ GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ @@ -403,7 +403,7 @@ run "test_case" { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) }), plan: &plans.Plan{ Changes: &plans.Changes{ @@ -479,7 +479,7 @@ run "test_case" { addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.NewDefaultProvider("test"), - }) + }, addrs.NoKey) }), plan: &plans.Plan{ Changes: &plans.Changes{ diff --git a/internal/tofu/transform_destroy_cbd_test.go b/internal/tofu/transform_destroy_cbd_test.go index 2f7fbd6a1a..887e21f544 100644 --- a/internal/tofu/transform_destroy_cbd_test.go +++ b/internal/tofu/transform_destroy_cbd_test.go @@ -105,6 +105,7 @@ func TestCBDEdgeTransformer(t *testing.T) { AttrsJSON: []byte(`{"id":"A"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.B").Resource, @@ -114,6 +115,7 @@ func TestCBDEdgeTransformer(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) g := cbdTestGraph(t, "transform-destroy-cbd-edge-basic", changes, state) @@ -166,6 +168,7 @@ func TestCBDEdgeTransformerMulti(t *testing.T) { AttrsJSON: []byte(`{"id":"A"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.B").Resource, @@ -174,6 +177,7 @@ func TestCBDEdgeTransformerMulti(t *testing.T) { AttrsJSON: []byte(`{"id":"B"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.C").Resource, @@ -186,6 +190,7 @@ func TestCBDEdgeTransformerMulti(t *testing.T) { }, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) g := cbdTestGraph(t, "transform-destroy-cbd-edge-multi", changes, state) @@ -242,6 +247,7 @@ func TestCBDEdgeTransformer_depNonCBDCount(t *testing.T) { AttrsJSON: []byte(`{"id":"A"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.B[0]").Resource, @@ -251,6 +257,7 @@ func TestCBDEdgeTransformer_depNonCBDCount(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.B[1]").Resource, @@ -260,6 +267,7 @@ func TestCBDEdgeTransformer_depNonCBDCount(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) g := cbdTestGraph(t, "transform-cbd-destroy-edge-count", changes, state) @@ -319,6 +327,7 @@ func TestCBDEdgeTransformer_depNonCBDCountBoth(t *testing.T) { AttrsJSON: []byte(`{"id":"A"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.A[1]").Resource, @@ -327,6 +336,7 @@ func TestCBDEdgeTransformer_depNonCBDCountBoth(t *testing.T) { AttrsJSON: []byte(`{"id":"A"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.B[0]").Resource, @@ -336,6 +346,7 @@ func TestCBDEdgeTransformer_depNonCBDCountBoth(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.B[1]").Resource, @@ -345,6 +356,7 @@ func TestCBDEdgeTransformer_depNonCBDCountBoth(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) g := cbdTestGraph(t, "transform-cbd-destroy-edge-both-count", changes, state) diff --git a/internal/tofu/transform_destroy_edge.go b/internal/tofu/transform_destroy_edge.go index 9dedc5ad8d..1d4c65dcbf 100644 --- a/internal/tofu/transform_destroy_edge.go +++ b/internal/tofu/transform_destroy_edge.go @@ -102,7 +102,7 @@ func (t *DestroyEdgeTransformer) tryInterProviderDestroyEdge(g *Graph, from, to // from the same provider instance. getComparableProvider := func(pc GraphNodeProviderConsumer) string { p := pc.ProvidedBy() - switch p := p.(type) { + switch p := p.ProviderConfig.(type) { case addrs.AbsProviderConfig: return p.String() case addrs.LocalProviderConfig: diff --git a/internal/tofu/transform_destroy_edge_test.go b/internal/tofu/transform_destroy_edge_test.go index 209d6a6090..ae4a95f1a3 100644 --- a/internal/tofu/transform_destroy_edge_test.go +++ b/internal/tofu/transform_destroy_edge_test.go @@ -33,6 +33,7 @@ func TestDestroyEdgeTransformer_basic(t *testing.T) { AttrsJSON: []byte(`{"id":"A"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.B").Resource, @@ -42,6 +43,7 @@ func TestDestroyEdgeTransformer_basic(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) if err := (&AttachStateTransformer{State: state}).Transform(&g); err != nil { t.Fatal(err) @@ -74,6 +76,7 @@ func TestDestroyEdgeTransformer_multi(t *testing.T) { AttrsJSON: []byte(`{"id":"A"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.B").Resource, @@ -83,6 +86,7 @@ func TestDestroyEdgeTransformer_multi(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.C").Resource, @@ -95,6 +99,7 @@ func TestDestroyEdgeTransformer_multi(t *testing.T) { }, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) if err := (&AttachStateTransformer{State: state}).Transform(&g); err != nil { @@ -143,6 +148,7 @@ func TestDestroyEdgeTransformer_module(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.test_object.b")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) child.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.b").Resource, @@ -151,6 +157,7 @@ func TestDestroyEdgeTransformer_module(t *testing.T) { AttrsJSON: []byte(`{"id":"b","test_string":"x"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) if err := (&AttachStateTransformer{State: state}).Transform(&g); err != nil { @@ -186,6 +193,7 @@ func TestDestroyEdgeTransformer_moduleOnly(t *testing.T) { AttrsJSON: []byte(`{"id":"a"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) child.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.b").Resource, @@ -197,6 +205,7 @@ func TestDestroyEdgeTransformer_moduleOnly(t *testing.T) { }, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) child.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.c").Resource, @@ -209,6 +218,7 @@ func TestDestroyEdgeTransformer_moduleOnly(t *testing.T) { }, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) } @@ -269,6 +279,7 @@ func TestDestroyEdgeTransformer_destroyThenUpdate(t *testing.T) { AttrsJSON: []byte(`{"id":"A","test_string":"old"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.B").Resource, @@ -278,6 +289,7 @@ func TestDestroyEdgeTransformer_destroyThenUpdate(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) if err := (&AttachStateTransformer{State: state}).Transform(&g); err != nil { @@ -367,6 +379,7 @@ func TestPruneUnusedNodesTransformer_rootModuleOutputValues(t *testing.T) { AttrsJSON: []byte(`{}`), }, providerCfgAddr, + addrs.NoKey, ) }) changes := plans.NewChanges() @@ -461,6 +474,7 @@ func TestDestroyEdgeTransformer_noOp(t *testing.T) { AttrsJSON: []byte(`{"id":"A"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.B").Resource, @@ -470,6 +484,7 @@ func TestDestroyEdgeTransformer_noOp(t *testing.T) { Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("test_object.C").Resource, @@ -480,6 +495,7 @@ func TestDestroyEdgeTransformer_noOp(t *testing.T) { mustConfigResourceAddr("test_object.B")}, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) if err := (&AttachStateTransformer{State: state}).Transform(&g); err != nil { @@ -540,6 +556,7 @@ func TestDestroyEdgeTransformer_dataDependsOn(t *testing.T) { AttrsJSON: []byte(`{"id":"A"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), + addrs.NoKey, ) if err := (&AttachStateTransformer{State: state}).Transform(&g); err != nil { diff --git a/internal/tofu/transform_import_state_test.go b/internal/tofu/transform_import_state_test.go index 42452809ad..35cfb9fc2f 100644 --- a/internal/tofu/transform_import_state_test.go +++ b/internal/tofu/transform_import_state_test.go @@ -46,10 +46,10 @@ func TestGraphNodeImportStateExecute(t *testing.T) { Name: "foo", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), ID: "bar", - ResolvedProvider: addrs.AbsProviderConfig{ + ResolvedProvider: ResolvedProvider{ProviderConfig: addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, - }, + }}, } diags := node.Execute(ctx, walkImport) @@ -102,10 +102,10 @@ func TestGraphNodeImportStateSubExecute(t *testing.T) { Name: "foo", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), State: importedResource, - ResolvedProvider: addrs.AbsProviderConfig{ + ResolvedProvider: ResolvedProvider{ProviderConfig: addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, - }, + }}, } diags := node.Execute(ctx, walkImport) if diags.HasErrors() { @@ -164,10 +164,10 @@ func TestGraphNodeImportStateSubExecuteNull(t *testing.T) { Name: "foo", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), State: importedResource, - ResolvedProvider: addrs.AbsProviderConfig{ + ResolvedProvider: ResolvedProvider{ProviderConfig: addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, - }, + }}, } diags := node.Execute(ctx, walkImport) if !diags.HasErrors() { diff --git a/internal/tofu/transform_orphan_count_test.go b/internal/tofu/transform_orphan_count_test.go index 72ea15c956..d9190cc41b 100644 --- a/internal/tofu/transform_orphan_count_test.go +++ b/internal/tofu/transform_orphan_count_test.go @@ -23,6 +23,7 @@ func TestOrphanResourceCountTransformer(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[0]").Resource, @@ -31,6 +32,7 @@ func TestOrphanResourceCountTransformer(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[2]").Resource, @@ -39,6 +41,7 @@ func TestOrphanResourceCountTransformer(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) g := Graph{Path: addrs.RootModuleInstance} @@ -74,6 +77,7 @@ func TestOrphanResourceCountTransformer_zero(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[0]").Resource, @@ -82,6 +86,7 @@ func TestOrphanResourceCountTransformer_zero(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[2]").Resource, @@ -90,6 +95,7 @@ func TestOrphanResourceCountTransformer_zero(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) g := Graph{Path: addrs.RootModuleInstance} @@ -125,6 +131,7 @@ func TestOrphanResourceCountTransformer_oneIndex(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[0]").Resource, @@ -133,6 +140,7 @@ func TestOrphanResourceCountTransformer_oneIndex(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[1]").Resource, @@ -141,6 +149,7 @@ func TestOrphanResourceCountTransformer_oneIndex(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) g := Graph{Path: addrs.RootModuleInstance} @@ -176,6 +185,7 @@ func TestOrphanResourceCountTransformer_deposed(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[0]").Resource, @@ -184,6 +194,7 @@ func TestOrphanResourceCountTransformer_deposed(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("aws_instance.foo[1]").Resource, @@ -192,6 +203,7 @@ func TestOrphanResourceCountTransformer_deposed(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) root.SetResourceInstanceDeposed( mustResourceInstanceAddr("aws_instance.foo[2]").Resource, @@ -201,6 +213,7 @@ func TestOrphanResourceCountTransformer_deposed(t *testing.T) { AttrsJSON: []byte(`{"id":"foo"}`), }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) g := Graph{Path: addrs.RootModuleInstance} @@ -246,6 +259,7 @@ func TestOrphanResourceCountTransformer_ForEachEdgesAdded(t *testing.T) { Status: states.ObjectReady, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) // NoKey'd resource @@ -262,6 +276,7 @@ func TestOrphanResourceCountTransformer_ForEachEdgesAdded(t *testing.T) { Status: states.ObjectReady, }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), + addrs.NoKey, ) }) diff --git a/internal/tofu/transform_orphan_resource_test.go b/internal/tofu/transform_orphan_resource_test.go index 1f326b7779..93c37e1768 100644 --- a/internal/tofu/transform_orphan_resource_test.go +++ b/internal/tofu/transform_orphan_resource_test.go @@ -35,6 +35,7 @@ func TestOrphanResourceInstanceTransformer(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) // The orphan @@ -54,6 +55,7 @@ func TestOrphanResourceInstanceTransformer(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) // A deposed orphan should not be handled by this transformer @@ -74,6 +76,7 @@ func TestOrphanResourceInstanceTransformer(t *testing.T) { Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) @@ -123,6 +126,7 @@ func TestOrphanResourceInstanceTransformer_countGood(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -140,6 +144,7 @@ func TestOrphanResourceInstanceTransformer_countGood(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) @@ -188,6 +193,7 @@ func TestOrphanResourceInstanceTransformer_countBad(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -205,6 +211,7 @@ func TestOrphanResourceInstanceTransformer_countBad(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) @@ -253,6 +260,7 @@ func TestOrphanResourceInstanceTransformer_modules(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) s.SetResourceInstanceCurrent( addrs.Resource{ @@ -270,6 +278,7 @@ func TestOrphanResourceInstanceTransformer_modules(t *testing.T) { Provider: addrs.NewDefaultProvider("aws"), Module: addrs.RootModule, }, + addrs.NoKey, ) }) diff --git a/internal/tofu/transform_provider.go b/internal/tofu/transform_provider.go index 2cda067fea..5e0fde27fd 100644 --- a/internal/tofu/transform_provider.go +++ b/internal/tofu/transform_provider.go @@ -13,7 +13,6 @@ import ( "github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/configs" "github.com/opentofu/opentofu/internal/dag" - "github.com/opentofu/opentofu/internal/providers" "github.com/opentofu/opentofu/internal/tfdiags" ) @@ -62,6 +61,22 @@ type GraphNodeCloseProvider interface { CloseProviderAddr() addrs.AbsProviderConfig } +type RequestedProvider struct { + ProviderConfig addrs.ProviderConfig + KeyExpression hcl.Expression + KeyModule addrs.Module + KeyResource bool + KeyExact addrs.InstanceKey +} + +type ResolvedProvider struct { + ProviderConfig addrs.AbsProviderConfig + KeyExpression hcl.Expression + KeyModule addrs.Module + KeyResource bool + KeyExact addrs.InstanceKey +} + // GraphNodeProviderConsumer is an interface that nodes that require // a provider must implement. ProvidedBy must return the address of the provider // to use, which will be resolved to a configuration either in the same module @@ -80,13 +95,13 @@ type GraphNodeProviderConsumer interface { // resolved elsewhere and must be referenced directly. No inheritence // logic is allowed. // Examples: state, resource instance (resolved), - ProvidedBy() addrs.ProviderConfig + ProvidedBy() RequestedProvider // Provider() returns the Provider FQN for the node. Provider() (provider addrs.Provider) // Set the resolved provider address for this resource. - SetProvider(addrs.AbsProviderConfig) + SetProvider(ResolvedProvider) } // ProviderTransformer is a GraphTransformer that maps resources to providers @@ -114,11 +129,11 @@ func (t *ProviderTransformer) Transform(g *Graph) error { continue } req := pv.ProvidedBy() - if req == nil { + if req.ProviderConfig == nil { // no provider is required continue } - switch providerAddr := req.(type) { + switch providerAddr := req.ProviderConfig.(type) { case addrs.AbsProviderConfig: target := m[providerAddr.String()] if target == nil { @@ -148,10 +163,18 @@ func (t *ProviderTransformer) Transform(g *Graph) error { // error instead. if p, ok := target.(*graphNodeProxyProvider); ok { target = p.Target() + // We are ignoring p.keyExpr here for now } log.Printf("[DEBUG] ProviderTransformer: %q (%T) needs exactly %s", dag.VertexName(v), v, dag.VertexName(target)) - pv.SetProvider(target.ProviderAddr()) + pv.SetProvider(ResolvedProvider{ + ProviderConfig: target.ProviderAddr(), + // Pass through key data + KeyExpression: req.KeyExpression, + KeyModule: req.KeyModule, + KeyResource: req.KeyResource, + KeyExact: req.KeyExact, + }) g.Connect(dag.BasicEdge(v, target)) case addrs.LocalProviderConfig: // We assume that the value returned from Provider() has already been @@ -197,13 +220,30 @@ func (t *ProviderTransformer) Transform(g *Graph) error { continue } + resolved := ResolvedProvider{ + KeyResource: req.KeyResource, + KeyExpression: req.KeyExpression, + } + // see if this is a proxy provider pointing to another concrete config if p, ok := target.(*graphNodeProxyProvider); ok { target = p.Target() + + targetExpr, targetPath := p.TargetExpr() + if targetExpr != nil { + if resolved.KeyResource { + // Module key and resource key are both required. This is not allowed! + diags = diags.Append(fmt.Errorf("provider instance key provided for both resource and module at %q, this is a bug and should be reported", dag.VertexName(v))) + continue + } + resolved.KeyExpression = targetExpr + resolved.KeyModule = targetPath + } } + resolved.ProviderConfig = target.ProviderAddr() log.Printf("[DEBUG] ProviderTransformer: %q (%T) needs %s", dag.VertexName(v), v, dag.VertexName(target)) - pv.SetProvider(target.ProviderAddr()) + pv.SetProvider(resolved) g.Connect(dag.BasicEdge(v, target)) default: panic(fmt.Sprintf("BUG: Invalid provider address type %T for %#v", req, req)) @@ -222,20 +262,26 @@ type ProviderFunctionReference struct { ProviderAlias string } +type FunctionProvidedBy struct { + Provider addrs.AbsProviderConfig + KeyModule addrs.Module + KeyExpression hcl.Expression +} + // ProviderFunctionMapping maps a provider used by functions at a given location in the graph to the actual AbsProviderConfig // that's required. This is due to the provider inheritence logic and proxy logic in the below // transformer needing to be known in other parts of the application. // Ideally, this would not be needed and be built like the ProviderTransformer. Unfortunately, it's // a significant refactor to get to that point which adds a lot of complexity. -type ProviderFunctionMapping map[ProviderFunctionReference]addrs.AbsProviderConfig +type ProviderFunctionMapping map[ProviderFunctionReference]FunctionProvidedBy -func (m ProviderFunctionMapping) Lookup(module addrs.Module, pf addrs.ProviderFunction) (addrs.AbsProviderConfig, bool) { - addr, ok := m[ProviderFunctionReference{ +func (m ProviderFunctionMapping) Lookup(module addrs.Module, pf addrs.ProviderFunction) (FunctionProvidedBy, bool) { + providedBy, ok := m[ProviderFunctionReference{ ModulePath: module.String(), ProviderName: pf.ProviderName, ProviderAlias: pf.ProviderAlias, }] - return addr, ok + return providedBy, ok } // ProviderFunctionTransformer is a GraphTransformer that maps nodes which reference functions to providers @@ -318,32 +364,8 @@ func (t *ProviderFunctionTransformer) Transform(g *Graph) error { // Providers with configuration will already exist within the graph and can be directly referenced log.Printf("[TRACE] ProviderFunctionTransformer: exact match for %s serving %s", absPc, dag.VertexName(v)) } else { - // At this point, all provider schemas should be loaded. We - // can now check to see if configuration is optional for this function. - providerSchema, ok := providers.SchemaCache.Get(absPc.Provider) - if !ok { - diags = diags.Append(&hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Unknown provider for function", - Detail: fmt.Sprintf("Provider %q does not have it's schema initialized", absPc.Provider), - Subject: ref.SourceRange.ToHCL().Ptr(), - }) - continue - } - _, functionOk := providerSchema.Functions[pf.Function] - if !functionOk { - diags = diags.Append(&hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Unknown provider function", - Detail: fmt.Sprintf("Provider %q does not have a function %q or has not been configured", absPc, pf.Function), - Subject: ref.SourceRange.ToHCL().Ptr(), - }) - continue - } - - // If this provider doesn't need to be configured then we can just - // stub it out with an init-only provider node, which will just - // start up the provider and fetch its schema. + // If this provider doesn't exist, stub it out with an init-only provider node + // This works for unconfigured functions only, but that validation is elsewhere stubAddr := addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: absPc.Provider, @@ -364,9 +386,13 @@ func (t *ProviderFunctionTransformer) Transform(g *Graph) error { } } + var targetExpr hcl.Expression + var targetPath addrs.Module + // see if this is a proxy provider pointing to another concrete config if p, ok := provider.(*graphNodeProxyProvider); ok { provider = p.Target() + targetExpr, targetPath = p.TargetExpr() } log.Printf("[DEBUG] ProviderFunctionTransformer: %q (%T) needs %s", dag.VertexName(v), v, dag.VertexName(provider)) @@ -374,7 +400,11 @@ func (t *ProviderFunctionTransformer) Transform(g *Graph) error { // Save for future lookups providerReferences[key] = provider - t.ProviderFunctionTracker[key] = provider.ProviderAddr() + t.ProviderFunctionTracker[key] = FunctionProvidedBy{ + Provider: provider.ProviderAddr(), + KeyModule: targetPath, + KeyExpression: targetExpr, + } } } } @@ -585,8 +615,9 @@ func (n *graphNodeCloseProvider) DotNode(name string, opts *dag.DotOpts) *dag.Do // configurations, and are removed after all the resources have been connected // to their providers. type graphNodeProxyProvider struct { - addr addrs.AbsProviderConfig - target GraphNodeProvider + addr addrs.AbsProviderConfig + target GraphNodeProvider + keyExpr hcl.Expression } var ( @@ -616,6 +647,25 @@ func (n *graphNodeProxyProvider) Target() GraphNodeProvider { } } +// Find the *single* keyExpression that is used in the provider +// chain. This is not ideal, but it works with current constraints on this feature +func (n *graphNodeProxyProvider) TargetExpr() (hcl.Expression, addrs.Module) { + switch t := n.target.(type) { + case *graphNodeProxyProvider: + targetExpr, targetPath := t.TargetExpr() + if targetExpr != nil && n.keyExpr != nil { + // This should have already been handled during provider validation + panic(fmt.Sprintf("BUG: Only one key expression allowed in module provider chain: %q", n.Name())) + } + if n.keyExpr != nil { + return n.keyExpr, n.ModulePath() + } + return targetExpr, targetPath + default: + return n.keyExpr, n.ModulePath() + } +} + // ProviderConfigTransformer adds all provider nodes from the configuration and // attaches the configs. type ProviderConfigTransformer struct { @@ -816,8 +866,9 @@ func (t *ProviderConfigTransformer) addProxyProviders(g *Graph, c *configs.Confi } proxy := &graphNodeProxyProvider{ - addr: fullAddr, - target: parentProvider, + addr: fullAddr, + target: parentProvider, + keyExpr: pair.InParent.KeyExpression, } concreteProvider := t.providers[fullName] diff --git a/internal/tofumigrate/tofumigrate_test.go b/internal/tofumigrate/tofumigrate_test.go index ded8cb1085..8354584305 100644 --- a/internal/tofumigrate/tofumigrate_test.go +++ b/internal/tofumigrate/tofumigrate_test.go @@ -57,6 +57,7 @@ func TestMigrateStateProviderAddresses(t *testing.T) { AttrsJSON: []byte(`{}`), }, makeRootProviderAddr("registry.terraform.io/hashicorp/random"), + addrs.NoKey, ) s.SetResourceInstanceCurrent( mustParseInstAddr("aws_instance.example"), @@ -65,6 +66,7 @@ func TestMigrateStateProviderAddresses(t *testing.T) { AttrsJSON: []byte(`{}`), }, makeRootProviderAddr("registry.terraform.io/hashicorp/aws"), + addrs.NoKey, ) }), }, @@ -76,6 +78,7 @@ func TestMigrateStateProviderAddresses(t *testing.T) { AttrsJSON: []byte(`{}`), }, makeRootProviderAddr("registry.opentofu.org/hashicorp/random"), + addrs.NoKey, ) s.SetResourceInstanceCurrent( mustParseInstAddr("aws_instance.example"), @@ -84,6 +87,7 @@ func TestMigrateStateProviderAddresses(t *testing.T) { AttrsJSON: []byte(`{}`), }, makeRootProviderAddr("registry.opentofu.org/hashicorp/aws"), + addrs.NoKey, ) }), }, @@ -99,6 +103,7 @@ func TestMigrateStateProviderAddresses(t *testing.T) { AttrsJSON: []byte(`{}`), }, makeRootProviderAddr("registry.terraform.io/hashicorp/random"), + addrs.NoKey, ) s.SetResourceInstanceCurrent( mustParseInstAddr("aws_instance.example"), @@ -107,6 +112,7 @@ func TestMigrateStateProviderAddresses(t *testing.T) { AttrsJSON: []byte(`{}`), }, makeRootProviderAddr("registry.terraform.io/hashicorp/aws"), + addrs.NoKey, ) }), }, @@ -118,6 +124,7 @@ func TestMigrateStateProviderAddresses(t *testing.T) { AttrsJSON: []byte(`{}`), }, makeRootProviderAddr("registry.opentofu.org/hashicorp/random"), + addrs.NoKey, ) s.SetResourceInstanceCurrent( mustParseInstAddr("aws_instance.example"), @@ -126,6 +133,7 @@ func TestMigrateStateProviderAddresses(t *testing.T) { AttrsJSON: []byte(`{}`), }, makeRootProviderAddr("registry.terraform.io/hashicorp/aws"), + addrs.NoKey, ) }), }, @@ -141,6 +149,7 @@ func TestMigrateStateProviderAddresses(t *testing.T) { AttrsJSON: []byte(`{}`), }, makeRootProviderAddr("registry.opentofu.org/hashicorp/random"), + addrs.NoKey, ) s.SetResourceInstanceCurrent( mustParseInstAddr("aws_instance.example"), @@ -149,6 +158,7 @@ func TestMigrateStateProviderAddresses(t *testing.T) { AttrsJSON: []byte(`{}`), }, makeRootProviderAddr("registry.opentofu.org/hashicorp/aws"), + addrs.NoKey, ) }), }, @@ -160,6 +170,7 @@ func TestMigrateStateProviderAddresses(t *testing.T) { AttrsJSON: []byte(`{}`), }, makeRootProviderAddr("registry.opentofu.org/hashicorp/random"), + addrs.NoKey, ) s.SetResourceInstanceCurrent( mustParseInstAddr("aws_instance.example"), @@ -168,6 +179,7 @@ func TestMigrateStateProviderAddresses(t *testing.T) { AttrsJSON: []byte(`{}`), }, makeRootProviderAddr("registry.opentofu.org/hashicorp/aws"), + addrs.NoKey, ) }), }, @@ -183,6 +195,7 @@ func TestMigrateStateProviderAddresses(t *testing.T) { AttrsJSON: []byte(`{}`), }, makeRootProviderAddr("registry.terraform.io/hashicorp/random"), + addrs.NoKey, ) s.SetResourceInstanceCurrent( mustParseInstAddr("aws_instance.example"), @@ -191,6 +204,7 @@ func TestMigrateStateProviderAddresses(t *testing.T) { AttrsJSON: []byte(`{}`), }, makeRootProviderAddr("registry.terraform.io/hashicorp/aws"), + addrs.NoKey, ) }), }, @@ -202,6 +216,7 @@ func TestMigrateStateProviderAddresses(t *testing.T) { AttrsJSON: []byte(`{}`), }, makeRootProviderAddr("registry.opentofu.org/hashicorp/random"), + addrs.NoKey, ) s.SetResourceInstanceCurrent( mustParseInstAddr("aws_instance.example"), @@ -210,6 +225,7 @@ func TestMigrateStateProviderAddresses(t *testing.T) { AttrsJSON: []byte(`{}`), }, makeRootProviderAddr("registry.opentofu.org/hashicorp/aws"), + addrs.NoKey, ) }), },