mirror of https://github.com/hashicorp/terraform
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1628 lines
56 KiB
1628 lines
56 KiB
// Copyright (c) HashiCorp, Inc.
|
|
// 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()
|
|
}
|
|
}
|