blob: 27dbd25d13eaf0b9ba3fdf444e4890b5cf297e93 [file] [log] [blame]
// 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: "Provider not found",
Detail: "Resource \"testing_resource.for_child\" not found in root module.",
}),
},
"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\" not found in 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,
},
},
},
}
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()
}
}