// 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, }, }, }, "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() } }