| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package stackmigrate | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "path/filepath" | |
| "sort" | |
| "strings" | |
| "testing" | |
| "github.com/google/go-cmp/cmp" | |
| "github.com/google/go-cmp/cmp/cmpopts" | |
| "github.com/hashicorp/go-slug/sourceaddrs" | |
| "github.com/hashicorp/go-slug/sourcebundle" | |
| "github.com/hashicorp/hcl/v2" | |
| "github.com/zclconf/go-cty-debug/ctydebug" | |
| "github.com/zclconf/go-cty/cty" | |
| "github.com/hashicorp/terraform/internal/addrs" | |
| "github.com/hashicorp/terraform/internal/collections" | |
| "github.com/hashicorp/terraform/internal/depsfile" | |
| "github.com/hashicorp/terraform/internal/getproviders/providerreqs" | |
| "github.com/hashicorp/terraform/internal/providers" | |
| "github.com/hashicorp/terraform/internal/stacks/stackaddrs" | |
| "github.com/hashicorp/terraform/internal/stacks/stackconfig" | |
| stacks_testing_provider "github.com/hashicorp/terraform/internal/stacks/stackruntime/testing" | |
| "github.com/hashicorp/terraform/internal/stacks/stackstate" | |
| "github.com/hashicorp/terraform/internal/states" | |
| "github.com/hashicorp/terraform/internal/tfdiags" | |
| ) | |
| func TestMigrate(t *testing.T) { | |
| deposedKey := states.NewDeposedKey() | |
| tcs := map[string]struct { | |
| path string | |
| state func(ss *states.SyncState) | |
| resources map[string]string | |
| modules map[string]string | |
| expected []stackstate.AppliedChange | |
| expectedDiags tfdiags.Diagnostics | |
| }{ | |
| "module": { | |
| path: filepath.Join("with-single-input", "valid"), | |
| state: func(ss *states.SyncState) { | |
| ss.SetResourceInstanceCurrent( | |
| addrs.AbsResourceInstance{ | |
| Module: addrs.ModuleInstance{ | |
| { | |
| Name: "child", | |
| }, | |
| }, | |
| Resource: addrs.ResourceInstance{ | |
| Resource: addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "data", | |
| }, | |
| Key: addrs.NoKey, | |
| }, | |
| }, | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceDeposed( | |
| addrs.AbsResourceInstance{ | |
| Module: addrs.ModuleInstance{ | |
| { | |
| Name: "child", | |
| }, | |
| }, | |
| Resource: addrs.ResourceInstance{ | |
| Resource: addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "data", | |
| }, | |
| Key: addrs.NoKey, | |
| }, | |
| }, | |
| deposedKey, | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| }, | |
| modules: map[string]string{ | |
| "child": "self", | |
| }, | |
| expected: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.self"), | |
| OutputValues: map[addrs.OutputValue]cty.Value{}, | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| {Name: "input"}: cty.DynamicVal, | |
| }, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.data"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: stackaddrs.AbsResourceInstanceObject{ | |
| Component: mustAbsResourceInstanceObject("component.self.testing_resource.data").Component, | |
| Item: addrs.AbsResourceInstanceObject{ | |
| ResourceInstance: mustAbsResourceInstanceObject("component.self.testing_resource.data").Item.ResourceInstance, | |
| DeposedKey: deposedKey, | |
| }, | |
| }, | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| }, | |
| }, | |
| "root resources": { | |
| path: filepath.Join("with-single-input", "valid"), | |
| state: func(ss *states.SyncState) { | |
| ss.SetResourceInstanceDeposed( | |
| addrs.AbsResourceInstance{ | |
| Module: addrs.RootModuleInstance, | |
| Resource: addrs.ResourceInstance{ | |
| Resource: addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "data", | |
| }, | |
| Key: addrs.NoKey, | |
| }, | |
| }, | |
| deposedKey, | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.AbsResourceInstance{ | |
| Module: addrs.RootModuleInstance, | |
| Resource: addrs.ResourceInstance{ | |
| Resource: addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "data", | |
| }, | |
| Key: addrs.NoKey, | |
| }, | |
| }, | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| }, | |
| resources: map[string]string{ | |
| "testing_resource.data": "component.self", | |
| }, | |
| expected: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.self"), | |
| OutputValues: map[addrs.OutputValue]cty.Value{}, | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| {Name: "input"}: cty.DynamicVal, | |
| }, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.data"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: stackaddrs.AbsResourceInstanceObject{ | |
| Component: mustAbsResourceInstanceObject("component.self.testing_resource.data").Component, | |
| Item: addrs.AbsResourceInstanceObject{ | |
| ResourceInstance: mustAbsResourceInstanceObject("component.self.testing_resource.data").Item.ResourceInstance, | |
| DeposedKey: deposedKey, | |
| }, | |
| }, | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| }, | |
| }, | |
| "component_dependency": { | |
| path: filepath.Join("for-stacks-migrate", "with-dependency", "input-dependency"), | |
| state: func(ss *states.SyncState) { | |
| ss.SetOutputValue(addrs.AbsOutputValue{ | |
| Module: addrs.RootModuleInstance, | |
| OutputValue: addrs.OutputValue{Name: "output"}, | |
| }, cty.StringVal("before"), false) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.AbsResourceInstance{ | |
| Module: addrs.RootModuleInstance, | |
| Resource: addrs.ResourceInstance{ | |
| Resource: addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "data", | |
| }, | |
| Key: addrs.NoKey, | |
| }, | |
| }, | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.AbsResourceInstance{ | |
| Module: addrs.RootModuleInstance, | |
| Resource: addrs.ResourceInstance{ | |
| Resource: addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "another", | |
| }, | |
| Key: addrs.IntKey(0), | |
| }, | |
| }, | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.AbsResourceInstance{ | |
| Module: addrs.RootModuleInstance, | |
| Resource: addrs.ResourceInstance{ | |
| Resource: addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "another", | |
| }, | |
| Key: addrs.IntKey(1), | |
| }, | |
| }, | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| }, | |
| resources: map[string]string{ | |
| "testing_resource.data": "component.parent", | |
| "testing_resource.another[0]": "component.child", | |
| "testing_resource.another[1]": "component.child", | |
| }, | |
| modules: map[string]string{}, | |
| expected: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.child"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.child"), | |
| OutputValues: map[addrs.OutputValue]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| }, | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| {Name: "input"}: cty.DynamicVal, | |
| }, | |
| Dependencies: collections.NewSet(mustAbsComponent("component.parent")), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.child.testing_resource.another[0]"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.child.testing_resource.another[1]"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.parent"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.parent"), | |
| OutputValues: map[addrs.OutputValue]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| }, | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| {Name: "input"}: cty.DynamicVal, | |
| }, | |
| Dependents: collections.NewSet(mustAbsComponent("component.child")), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.data"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| }, | |
| }, | |
| "nested module resources": { | |
| path: filepath.Join("for-stacks-migrate", "with-nested-module"), | |
| state: func(ss *states.SyncState) { | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "data", | |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "another", | |
| }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "another", | |
| }.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| for _, child := range []string{"child_mod", "child_mod2"} { | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "child_data", | |
| }.Instance(addrs.NoKey).Absolute(addrs.ModuleInstance{ | |
| { | |
| Name: child, | |
| }, | |
| }), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "another_child_data", | |
| }.Instance(addrs.IntKey(0)).Absolute(addrs.ModuleInstance{ | |
| { | |
| Name: child, | |
| }, | |
| }), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "another_child_data", | |
| }.Instance(addrs.IntKey(1)).Absolute(addrs.ModuleInstance{ | |
| { | |
| Name: child, | |
| }, | |
| }), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| } | |
| }, | |
| resources: map[string]string{ | |
| "testing_resource.data": "component.parent", | |
| "testing_resource.another[0]": "component.parent", | |
| "testing_resource.another[1]": "component.parent", | |
| }, | |
| modules: map[string]string{ | |
| "child_mod": "child", | |
| "child_mod2": "child2", | |
| }, | |
| expected: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.child"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.child"), | |
| OutputValues: map[addrs.OutputValue]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| }, | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| {Name: "input"}: cty.DynamicVal, | |
| }, | |
| Dependencies: collections.NewSet(mustAbsComponent("component.parent")), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.child.testing_resource.another_child_data[0]"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.child.testing_resource.another_child_data[1]"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.child.testing_resource.child_data"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.child2"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.child2"), | |
| OutputValues: map[addrs.OutputValue]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| }, | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| {Name: "input"}: cty.DynamicVal, | |
| }, | |
| Dependencies: collections.NewSet(mustAbsComponent("component.parent")), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.child2.testing_resource.another_child_data[0]"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.child2.testing_resource.another_child_data[1]"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.child2.testing_resource.child_data"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.parent"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.parent"), | |
| OutputValues: map[addrs.OutputValue]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| }, | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| {Name: "input"}: cty.DynamicVal, | |
| }, | |
| Dependents: collections.NewSet(mustAbsComponent("component.child"), mustAbsComponent("component.child2")), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.another[0]"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.another[1]"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.data"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| }, | |
| }, | |
| "deeply nested module resources with implicit provider configs": { | |
| path: filepath.Join("for-stacks-migrate", "with-deeply-nested-module"), | |
| state: func(ss *states.SyncState) { | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "data", | |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "another", | |
| }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "another", | |
| }.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| }, | |
| resources: map[string]string{ | |
| "testing_resource.data": "component.parent.module.child_mod.module.grand_child_mod.testing_resource.grand_child_data", | |
| "testing_resource.another[0]": "component.parent.module.child_mod.module.grand_child_mod.testing_resource.another_grand_child_data[0]", | |
| "testing_resource.another[1]": "component.parent.module.child_mod.module.grand_child_mod.testing_resource.another_grand_child_data[1]", | |
| }, | |
| expected: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.parent"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.parent"), | |
| OutputValues: map[addrs.OutputValue]cty.Value{}, | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| {Name: "input"}: cty.DynamicVal, | |
| }, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.module.child_mod.module.grand_child_mod.testing_resource.another_grand_child_data[0]"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.module.child_mod.module.grand_child_mod.testing_resource.another_grand_child_data[1]"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.module.child_mod.module.grand_child_mod.testing_resource.grand_child_data"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| }, | |
| }, | |
| "missing config resource": { | |
| path: filepath.Join("for-stacks-migrate", "with-nested-module"), | |
| state: func(ss *states.SyncState) { | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "data", | |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "another", | |
| }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "another", | |
| }.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "for_child", | |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| }, | |
| resources: map[string]string{ | |
| "testing_resource.data": "component.parent", | |
| "testing_resource.another[0]": "component.parent", | |
| "testing_resource.another[1]": "component.parent", | |
| "testing_resource.for_child": "component.child", | |
| }, | |
| expected: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.child"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.child"), | |
| OutputValues: map[addrs.OutputValue]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| }, | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| {Name: "input"}: cty.DynamicVal, | |
| }, | |
| Dependencies: collections.NewSet(mustAbsComponent("component.parent")), | |
| }, | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.parent"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.parent"), | |
| OutputValues: map[addrs.OutputValue]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| }, | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| {Name: "input"}: cty.DynamicVal, | |
| }, | |
| Dependents: collections.NewSet(mustAbsComponent("component.child")), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.another[0]"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.another[1]"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.data"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| }, | |
| expectedDiags: tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Resource mapped to non-existent target", | |
| Detail: "Could not migrate resource \"testing_resource.for_child\". Target resource \"testing_resource.for_child\" not found in component \"component.child\".", | |
| }), | |
| }, | |
| "missing mapping for state resource": { | |
| path: filepath.Join("for-stacks-migrate", "with-nested-module"), | |
| state: func(ss *states.SyncState) { | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "data", | |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "another", | |
| }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "another", | |
| }.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "for_child", | |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| }, | |
| resources: map[string]string{ | |
| "testing_resource.data": "component.parent", | |
| "testing_resource.another[0]": "component.parent", | |
| "testing_resource.another[1]": "component.parent", | |
| }, | |
| modules: map[string]string{}, | |
| expected: []stackstate.AppliedChange{ | |
| // this component has a dependent "child", but that other component | |
| // is not present in the modules mapping, so it is not included here | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.parent"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.parent"), | |
| OutputValues: map[addrs.OutputValue]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| }, | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| {Name: "id"}: cty.DynamicVal, | |
| {Name: "input"}: cty.DynamicVal, | |
| }, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.another[0]"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.another[1]"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.data"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| }, | |
| expectedDiags: tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Resource not found", | |
| Detail: "Resource \"testing_resource.for_child\" exists in state, but was not included in any provided mapping.", | |
| }), | |
| }, | |
| "config depends on": { | |
| path: filepath.Join("for-stacks-migrate", "with-depends-on"), | |
| state: func(ss *states.SyncState) { | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "data", | |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "depends_test", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "second", | |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "depends_test", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "third", | |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "depends_test", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| }, | |
| resources: map[string]string{ | |
| "testing_resource.data": "component.first", | |
| "testing_resource.second": "component.second", | |
| "testing_resource.third": "component.second", | |
| }, | |
| modules: map[string]string{}, | |
| expected: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.first"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.first"), | |
| OutputValues: make(map[addrs.OutputValue]cty.Value), | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| {Name: "input"}: cty.DynamicVal, | |
| {Name: "id"}: cty.DynamicVal, | |
| }, | |
| Dependents: collections.NewSet(mustAbsComponent("component.second")), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.first.testing_resource.data"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "depends_test", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.second"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.second"), | |
| OutputValues: make(map[addrs.OutputValue]cty.Value), | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| {Name: "input"}: cty.DynamicVal, | |
| {Name: "id"}: cty.DynamicVal, | |
| }, | |
| Dependencies: collections.NewSet(mustAbsComponent("component.first")), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.second.testing_resource.second"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "depends_test", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.second.testing_resource.third"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "depends_test", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| }, | |
| expectedDiags: tfdiags.Diagnostics{}.Append(), | |
| }, | |
| "unsupported component ref": { | |
| path: filepath.Join("for-stacks-migrate", "with-depends-on"), | |
| state: func(ss *states.SyncState) { | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "data", | |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "depends_test", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "second", | |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "depends_test", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "third", | |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "depends_test", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| }, | |
| resources: map[string]string{ | |
| "testing_resource.data": "component.first", | |
| "testing_resource.second": "component.second", | |
| "testing_resource.third": "stack.embedded.component.self", | |
| }, | |
| modules: map[string]string{}, | |
| expected: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.first"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.first"), | |
| OutputValues: make(map[addrs.OutputValue]cty.Value), | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| {Name: "input"}: cty.DynamicVal, | |
| {Name: "id"}: cty.DynamicVal, | |
| }, | |
| Dependents: collections.NewSet(mustAbsComponent("component.second")), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.first.testing_resource.data"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "depends_test", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.second"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.second"), | |
| OutputValues: make(map[addrs.OutputValue]cty.Value), | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| {Name: "input"}: cty.DynamicVal, | |
| {Name: "id"}: cty.DynamicVal, | |
| }, | |
| Dependencies: collections.NewSet(mustAbsComponent("component.first")), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.second.testing_resource.second"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "depends_test", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| }, | |
| expectedDiags: tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Invalid component instance", | |
| Detail: "Only root component instances are allowed, got \"stack.embedded.component.self\"", | |
| }), | |
| }, | |
| "child module as component source": { | |
| path: filepath.Join("for-stacks-migrate", "child-module-as-component-source"), | |
| state: func(ss *states.SyncState) { | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "root_id", | |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "root_id", | |
| "value": "root_output", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| childProv := mustDefaultRootProvider("testing") | |
| childProv.Module = addrs.Module{"child_module"} | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "child_data", | |
| }.Instance(addrs.NoKey).Absolute(addrs.ModuleInstance{ | |
| { | |
| Name: "child_module", | |
| }, | |
| }), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "child_data", | |
| "value": "child_output", | |
| }), | |
| }, | |
| childProv, | |
| ) | |
| }, | |
| resources: map[string]string{ | |
| "testing_resource.root_id": "component.self", | |
| "testing_resource.child_data": "component.self", // this should just be ignored | |
| }, | |
| modules: map[string]string{ | |
| "child_module": "triage", | |
| }, | |
| expected: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.self"), | |
| OutputValues: map[addrs.OutputValue]cty.Value{}, | |
| InputVariables: map[addrs.InputVariable]cty.Value{}, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.root_id"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "root_id", | |
| "value": "root_output", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.triage"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.triage"), | |
| OutputValues: map[addrs.OutputValue]cty.Value{}, | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| addrs.InputVariable{Name: "input"}: cty.DynamicVal, | |
| }, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.triage.testing_resource.child_data"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "child_data", | |
| "value": "child_output", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| }, | |
| }, | |
| "unclaimed resources fall into modules": { | |
| path: filepath.Join("for-stacks-migrate", "multiple-components"), | |
| state: func(ss *states.SyncState) { | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "one", | |
| }.Instance(addrs.NoKey).Absolute(addrs.ModuleInstance{ | |
| { | |
| Name: "self", | |
| }, | |
| }), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "one", | |
| "value": "one", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "resource", | |
| }.Instance(addrs.NoKey).Absolute(addrs.ModuleInstance{ | |
| { | |
| Name: "self", | |
| }, | |
| }), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "two", | |
| "value": "two", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| }, | |
| resources: map[string]string{ | |
| // this specific resource goes to component.one | |
| "module.self.testing_resource.one": "component.one.testing_resource.resource", | |
| }, | |
| modules: map[string]string{ | |
| "self": "two", // all other resources go to component.two | |
| }, | |
| expected: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.one"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.one"), | |
| OutputValues: map[addrs.OutputValue]cty.Value{}, | |
| InputVariables: map[addrs.InputVariable]cty.Value{}, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.one.testing_resource.resource"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "one", | |
| "value": "one", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.two"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.two"), | |
| OutputValues: map[addrs.OutputValue]cty.Value{}, | |
| InputVariables: map[addrs.InputVariable]cty.Value{}, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.two.testing_resource.resource"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "two", | |
| "value": "two", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| }, | |
| }, | |
| "single component": { | |
| path: filepath.Join("for-stacks-migrate", "single-component"), | |
| state: func(ss *states.SyncState) { | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "one", | |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "one", | |
| "value": "one", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "two", | |
| }.Instance(addrs.NoKey).Absolute(addrs.ModuleInstance{ | |
| { | |
| Name: "two", | |
| }, | |
| }), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "two", | |
| "value": "two", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| ss.SetResourceInstanceCurrent( | |
| addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "testing_resource", | |
| Name: "three", | |
| }.Instance(addrs.NoKey).Absolute(addrs.ModuleInstance{ | |
| { | |
| Name: "three", | |
| }, | |
| }), | |
| &states.ResourceInstanceObjectSrc{ | |
| Status: states.ObjectReady, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "three", | |
| "value": "three", | |
| }), | |
| }, | |
| mustDefaultRootProvider("testing"), | |
| ) | |
| }, | |
| resources: map[string]string{ | |
| "testing_resource.one": "component.single.testing_resource.one", | |
| }, | |
| modules: map[string]string{ | |
| "two": "single", | |
| "three": "single", | |
| }, expected: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.single"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.single"), | |
| OutputValues: map[addrs.OutputValue]cty.Value{}, | |
| InputVariables: map[addrs.InputVariable]cty.Value{}, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.single.testing_resource.one"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "one", | |
| "value": "one", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.single.testing_resource.three"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "three", | |
| "value": "three", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.single.testing_resource.two"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "two", | |
| "value": "two", | |
| }), | |
| Status: states.ObjectReady, | |
| Private: nil, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| }, | |
| }, | |
| } | |
| for name, tc := range tcs { | |
| t.Run(name, func(t *testing.T) { | |
| cfg := loadMainBundleConfigForTest(t, tc.path) | |
| lock := depsfile.NewLocks() | |
| lock.SetProvider( | |
| addrs.NewDefaultProvider("testing"), | |
| providerreqs.MustParseVersion("0.0.0"), | |
| providerreqs.MustParseVersionConstraints("=0.0.0"), | |
| providerreqs.PreferredHashes([]providerreqs.Hash{}), | |
| ) | |
| state := states.BuildState(tc.state) | |
| migration := Migration{ | |
| Providers: map[addrs.Provider]providers.Factory{ | |
| addrs.NewDefaultProvider("testing"): func() (providers.Interface, error) { | |
| return stacks_testing_provider.NewProvider(t), nil | |
| }, | |
| }, | |
| PreviousState: state, | |
| Config: cfg, | |
| } | |
| var applied []stackstate.AppliedChange | |
| var gotDiags tfdiags.Diagnostics | |
| migration.Migrate(tc.resources, tc.modules, func(change stackstate.AppliedChange) { | |
| applied = append(applied, change) | |
| }, func(diagnostic tfdiags.Diagnostic) { | |
| gotDiags = append(gotDiags, diagnostic) | |
| }) | |
| sort.SliceStable(applied, func(i, j int) bool { | |
| key := func(change stackstate.AppliedChange) string { | |
| switch change := change.(type) { | |
| case *stackstate.AppliedChangeComponentInstance: | |
| return change.ComponentInstanceAddr.String() | |
| case *stackstate.AppliedChangeResourceInstanceObject: | |
| return change.ResourceInstanceObjectAddr.String() | |
| default: | |
| panic("unsupported change type") | |
| } | |
| } | |
| return key(applied[i]) < key(applied[j]) | |
| }) | |
| if diff := cmp.Diff(tc.expected, applied, cmp.Options{ | |
| ctydebug.CmpOptions, | |
| collections.CmpOptions, | |
| cmpopts.IgnoreUnexported(addrs.InputVariable{}), | |
| cmpopts.IgnoreUnexported(states.ResourceInstanceObjectSrc{}), | |
| }); len(diff) > 0 { | |
| t.Errorf("unexpected applied changes:\n%s", diff) | |
| } | |
| tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.expectedDiags) | |
| }) | |
| } | |
| } | |
| func mustMarshalJSONAttrs(attrs map[string]interface{}) []byte { | |
| jsonAttrs, err := json.Marshal(attrs) | |
| if err != nil { | |
| panic(err) | |
| } | |
| return jsonAttrs | |
| } | |
| func mustDefaultRootProvider(provider string) addrs.AbsProviderConfig { | |
| return addrs.AbsProviderConfig{ | |
| Module: addrs.RootModule, | |
| Provider: addrs.NewDefaultProvider(provider), | |
| } | |
| } | |
| func mustAbsResourceInstanceObject(addr string) stackaddrs.AbsResourceInstanceObject { | |
| ret, diags := stackaddrs.ParseAbsResourceInstanceObjectStr(addr) | |
| if len(diags) > 0 { | |
| panic(fmt.Sprintf("failed to parse resource instance object address %q: %s", addr, diags)) | |
| } | |
| return ret | |
| } | |
| func mustAbsComponentInstance(addr string) stackaddrs.AbsComponentInstance { | |
| ret, diags := stackaddrs.ParsePartialComponentInstanceStr(addr) | |
| if len(diags) > 0 { | |
| panic(fmt.Sprintf("failed to parse component instance address %q: %s", addr, diags)) | |
| } | |
| return ret | |
| } | |
| func mustAbsComponent(addr string) stackaddrs.AbsComponent { | |
| ret, diags := stackaddrs.ParsePartialComponentInstanceStr(addr) | |
| if len(diags) > 0 { | |
| panic(fmt.Sprintf("failed to parse component instance address %q: %s", addr, diags)) | |
| } | |
| return stackaddrs.AbsComponent{ | |
| Stack: ret.Stack, | |
| Item: ret.Item.Component, | |
| } | |
| } | |
| // TODO: Perhaps export this from helper_test instead | |
| func loadMainBundleConfigForTest(t *testing.T, dirName string) *stackconfig.Config { | |
| t.Helper() | |
| fullSourceAddr := mainBundleSourceAddrStr(dirName) | |
| return loadConfigForTest(t, "../stackruntime/testdata/mainbundle", fullSourceAddr) | |
| } | |
| func mainBundleSourceAddrStr(dirName string) string { | |
| return "git::https://example.com/test.git//" + dirName | |
| } | |
| // loadConfigForTest is a test helper that tries to open bundleRoot as a | |
| // source bundle, and then if successful tries to load the given source address | |
| // from it as a stack configuration. If any part of the operation fails then | |
| // it halts execution of the test and doesn't return. | |
| func loadConfigForTest(t *testing.T, bundleRoot string, configSourceAddr string) *stackconfig.Config { | |
| t.Helper() | |
| sources, err := sourcebundle.OpenDir(bundleRoot) | |
| if err != nil { | |
| t.Fatalf("cannot load source bundle: %s", err) | |
| } | |
| // We force using remote source addresses here because that avoids | |
| // us having to deal with the extra version constraints argument | |
| // that registry sources require. Exactly what source address type | |
| // we use isn't relevant for tests in this package, since it's | |
| // the sourcebundle package's responsibility to make sure its | |
| // abstraction works for all of the source types. | |
| sourceAddr, err := sourceaddrs.ParseRemoteSource(configSourceAddr) | |
| if err != nil { | |
| t.Fatalf("invalid config source address: %s", err) | |
| } | |
| cfg, diags := stackconfig.LoadConfigDir(sourceAddr, sources) | |
| reportDiagnosticsForTest(t, diags) | |
| return cfg | |
| } | |
| // reportDiagnosticsForTest creates a test log entry for every diagnostic in | |
| // the given diags, and halts the test if any of them are error diagnostics. | |
| func reportDiagnosticsForTest(t *testing.T, diags tfdiags.Diagnostics) { | |
| t.Helper() | |
| for _, diag := range diags { | |
| var b strings.Builder | |
| desc := diag.Description() | |
| locs := diag.Source() | |
| switch sev := diag.Severity(); sev { | |
| case tfdiags.Error: | |
| b.WriteString("Error: ") | |
| case tfdiags.Warning: | |
| b.WriteString("Warning: ") | |
| default: | |
| t.Errorf("unsupported diagnostic type %s", sev) | |
| } | |
| b.WriteString(desc.Summary) | |
| if desc.Address != "" { | |
| b.WriteString("\nwith ") | |
| b.WriteString(desc.Summary) | |
| } | |
| if locs.Subject != nil { | |
| b.WriteString("\nat ") | |
| b.WriteString(locs.Subject.StartString()) | |
| } | |
| if desc.Detail != "" { | |
| b.WriteString("\n\n") | |
| b.WriteString(desc.Detail) | |
| } | |
| t.Log(b.String()) | |
| } | |
| if diags.HasErrors() { | |
| t.FailNow() | |
| } | |
| } |