2023-09-27 20:00:36 -04:00
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
2023-07-21 20:13:35 -04:00
package stackruntime
import (
"context"
2024-02-15 04:45:47 -05:00
"encoding/json"
2023-07-25 20:16:13 -04:00
"fmt"
2024-02-15 04:45:47 -05:00
"path"
2024-09-07 08:54:32 -04:00
"path/filepath"
2023-07-25 20:16:13 -04:00
"sort"
2024-02-15 04:45:47 -05:00
"strings"
2023-07-21 20:13:35 -04:00
"testing"
2023-10-04 18:35:26 -04:00
"time"
2023-07-21 20:13:35 -04:00
"github.com/google/go-cmp/cmp"
2024-02-12 11:24:15 -05:00
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty-debug/ctydebug"
"github.com/zclconf/go-cty/cty"
2024-06-04 09:14:00 -04:00
"github.com/hashicorp/terraform/internal/checks"
2024-06-27 10:08:08 -04:00
"github.com/hashicorp/terraform/internal/depsfile"
"github.com/hashicorp/terraform/internal/getproviders/providerreqs"
2024-07-01 08:23:17 -04:00
"github.com/hashicorp/terraform/internal/stacks/stackruntime/hooks"
2024-06-04 09:14:00 -04:00
2023-07-25 19:23:42 -04:00
"github.com/hashicorp/terraform/internal/addrs"
2026-02-02 10:38:27 -05:00
2023-08-30 20:13:16 -04:00
terraformProvider "github.com/hashicorp/terraform/internal/builtin/providers/terraform"
2023-12-08 21:52:24 -05:00
"github.com/hashicorp/terraform/internal/collections"
2026-02-02 10:38:27 -05:00
"github.com/hashicorp/terraform/internal/configs"
2023-08-22 21:31:10 -04:00
"github.com/hashicorp/terraform/internal/configs/configschema"
2023-12-13 19:38:22 -05:00
"github.com/hashicorp/terraform/internal/lang/marks"
2023-07-21 20:13:35 -04:00
"github.com/hashicorp/terraform/internal/plans"
2023-08-22 21:31:10 -04:00
"github.com/hashicorp/terraform/internal/providers"
2024-02-16 04:46:50 -05:00
default_testing_provider "github.com/hashicorp/terraform/internal/providers/testing"
2023-07-21 20:13:35 -04:00
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
"github.com/hashicorp/terraform/internal/stacks/stackplan"
2024-06-12 21:47:31 -04:00
"github.com/hashicorp/terraform/internal/stacks/stackruntime/internal/stackeval"
2024-02-16 04:46:50 -05:00
stacks_testing_provider "github.com/hashicorp/terraform/internal/stacks/stackruntime/testing"
2024-02-15 04:45:47 -05:00
"github.com/hashicorp/terraform/internal/stacks/stackstate"
"github.com/hashicorp/terraform/internal/states"
2023-07-21 20:13:35 -04:00
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/hashicorp/terraform/version"
)
2024-02-26 04:42:14 -05:00
// TestPlan_valid runs the same set of configurations as TestValidate_valid.
//
// Plan should execute the same set of validations as validate, so we expect
// all of the following to be valid for both plan and validate.
//
// We also want to make sure the static and dynamic evaluations are not
// returning duplicate / conflicting diagnostics. This test will tell us if
// either plan or validate is reporting diagnostics the others are missing.
func TestPlan_valid ( t * testing . T ) {
for name , tc := range validConfigurations {
t . Run ( name , func ( t * testing . T ) {
if tc . skip {
// We've added this test before the implementation was ready.
t . SkipNow ( )
}
ctx := context . Background ( )
2024-06-27 10:08:08 -04:00
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-07-26 05:36:08 -04:00
lock . SetProvider (
addrs . NewDefaultProvider ( "other" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-09-12 10:16:15 -04:00
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
testContext := TestContext {
config : loadMainBundleConfigForTest ( t , name ) ,
providers : map [ addrs . Provider ] providers . Factory {
2024-02-28 02:24:53 -05:00
// We support both hashicorp/testing and
// terraform.io/builtin/testing as providers. This lets us
// test the provider aliasing feature. Both providers
// support the same set of resources and data sources.
2024-02-26 05:36:19 -05:00
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-02-26 05:36:19 -05:00
} ,
2024-02-28 02:24:53 -05:00
addrs . NewBuiltInProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-02-28 02:24:53 -05:00
} ,
2024-07-26 05:36:08 -04:00
// We also support an "other" provider out of the box to
// test the provider aliasing feature.
addrs . NewDefaultProvider ( "other" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-07-26 05:36:08 -04:00
} ,
2024-02-26 05:36:19 -05:00
} ,
2024-09-12 10:16:15 -04:00
dependencyLocks : * lock ,
timestamp : & fakePlanTimestamp ,
2024-02-26 04:42:14 -05:00
}
2024-09-12 10:16:15 -04:00
cycle := TestCycle {
planInputs : tc . planInputVars ,
wantPlannedChanges : nil , // don't care about the planned changes in this test.
wantPlannedDiags : nil , // should return no diagnostics.
2024-02-26 04:42:14 -05:00
}
2024-09-12 10:16:15 -04:00
testContext . Plan ( t , ctx , nil , cycle )
2024-02-26 04:42:14 -05:00
} )
}
}
// TestPlan_invalid runs the same set of configurations as TestValidate_invalid.
//
// Plan should execute the same set of validations as validate, so we expect
// all of the following to be invalid for both plan and validate.
//
// We also want to make sure the static and dynamic evaluations are not
// returning duplicate / conflicting diagnostics. This test will tell us if
// either plan or validate is reporting diagnostics the others are missing.
//
// The dynamic validation that happens during the plan *might* introduce
// additional diagnostics that are not present in the static validation. These
// should be added manually into this function.
func TestPlan_invalid ( t * testing . T ) {
for name , tc := range invalidConfigurations {
t . Run ( name , func ( t * testing . T ) {
if tc . skip {
// We've added this test before the implementation was ready.
t . SkipNow ( )
}
ctx := context . Background ( )
2024-06-27 10:08:08 -04:00
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-09-12 10:16:15 -04:00
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
testContext := TestContext {
config : loadMainBundleConfigForTest ( t , name ) ,
providers : map [ addrs . Provider ] providers . Factory {
2024-02-28 02:24:53 -05:00
// We support both hashicorp/testing and
// terraform.io/builtin/testing as providers. This lets us
// test the provider aliasing feature. Both providers
// support the same set of resources and data sources.
2024-02-26 04:42:14 -05:00
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-02-26 04:42:14 -05:00
} ,
2024-02-28 02:24:53 -05:00
addrs . NewBuiltInProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-02-28 02:24:53 -05:00
} ,
2024-02-26 04:42:14 -05:00
} ,
2024-09-12 10:16:15 -04:00
dependencyLocks : * lock ,
timestamp : & fakePlanTimestamp ,
2024-02-26 04:42:14 -05:00
}
2024-09-12 10:16:15 -04:00
cycle := TestCycle {
planInputs : tc . planInputVars ,
wantPlannedChanges : nil , // don't care about the planned changes in this test.
wantPlannedDiags : tc . diags ( ) ,
2024-02-26 04:42:14 -05:00
}
2024-09-12 10:16:15 -04:00
testContext . Plan ( t , ctx , nil , cycle )
2024-02-26 04:42:14 -05:00
} )
}
}
2024-10-09 10:32:54 -04:00
// TestPlan uses a generic framework for running plan integration tests
// against Stacks. Generally, new tests should be added into this function
// rather than copying the large amount of duplicate code from the other
// tests in this file.
//
// If you are editing other tests in this file, please consider moving them
// into this test function so they can reuse the shared setup and boilerplate
// code managing the boring parts of the test.
func TestPlan ( t * testing . T ) {
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
tcs := map [ string ] struct {
path string
state * stackstate . State
store * stacks_testing_provider . ResourceStore
cycle TestCycle
} {
"empty-destroy-with-data-source" : {
path : path . Join ( "with-data-source" , "dependent" ) ,
cycle : TestCycle {
planMode : plans . DestroyMode ,
planInputs : map [ string ] cty . Value {
"id" : cty . StringVal ( "foo" ) ,
} ,
wantPlannedChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.data" ) ,
PlanApplyable : true ,
PlanComplete : true ,
Action : plans . Delete ,
Mode : plans . DestroyMode ,
RequiredComponents : collections . NewSet ( mustAbsComponent ( "component.self" ) ) ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.self" ) ,
PlanComplete : true ,
PlanApplyable : true ,
Action : plans . Delete ,
Mode : plans . DestroyMode ,
PlannedOutputValues : map [ string ] cty . Value {
2025-03-18 04:28:31 -04:00
"id" : cty . StringVal ( "foo" ) ,
2024-10-09 10:32:54 -04:00
} ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : mustStackInputVariable ( "id" ) ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . StringVal ( "foo" ) ,
DeleteOnApply : true ,
} ,
} ,
} ,
} ,
2025-04-02 09:58:42 -04:00
"deferred-provider-with-write-only" : {
path : "with-write-only-attribute" ,
cycle : TestCycle {
planInputs : map [ string ] cty . Value {
"providers" : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
} ,
wantPlannedChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.main" ) ,
Action : plans . Create ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"datasource_id" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "datasource" ) ) ,
"resource_id" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "resource" ) ) ,
"write_only_input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "secret" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"datasource_id" : nil ,
"resource_id" : nil ,
"write_only_input" : nil ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeDeferredResourceInstancePlanned {
ResourceInstancePlanned : stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.main.data.testing_write_only_data_source.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "data.testing_write_only_data_source.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "data.testing_write_only_data_source.data" ) ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Read ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "datasource" ) ,
"value" : cty . UnknownVal ( cty . String ) ,
"write_only" : cty . NullVal ( cty . String ) ,
} ) ) ,
AfterSensitivePaths : [ ] cty . Path {
cty . GetAttrPath ( "write_only" ) ,
} ,
} ,
ActionReason : plans . ResourceInstanceReadBecauseDependencyPending ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . WriteOnlyDataSourceSchema ,
} ,
DeferredReason : providers . DeferredReasonProviderConfigUnknown ,
} ,
& stackplan . PlannedChangeDeferredResourceInstancePlanned {
ResourceInstancePlanned : stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.main.testing_write_only_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_write_only_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_write_only_resource.data" ) ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "resource" ) ,
"value" : cty . UnknownVal ( cty . String ) ,
"write_only" : cty . NullVal ( cty . String ) ,
} ) ) ,
AfterSensitivePaths : [ ] cty . Path {
cty . GetAttrPath ( "write_only" ) ,
} ,
} ,
} ,
PriorStateSrc : nil ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . WriteOnlyResourceSchema ,
} ,
DeferredReason : providers . DeferredReasonProviderConfigUnknown ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : mustStackInputVariable ( "providers" ) ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
} ,
} ,
} ,
} ,
2024-10-22 05:23:53 -04:00
"deferred-provider-with-data-sources" : {
path : path . Join ( "with-data-source" , "deferred-provider-for-each" ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "data_known" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "data_known" ) ,
"value" : cty . StringVal ( "known" ) ,
} ) ) .
Build ( ) ,
cycle : TestCycle {
planInputs : map [ string ] cty . Value {
"providers" : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
} ,
wantPlannedChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.const" ) ,
PlanApplyable : true ,
PlanComplete : true ,
Action : plans . Create ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "data_known" ) ) ,
"resource" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "resource_known" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"resource" : nil ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.const.data.testing_data_source.data" ) ,
ChangeSrc : nil ,
PriorStateSrc : & states . ResourceInstanceObjectSrc {
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] interface { } {
"id" : "data_known" ,
"value" : "known" ,
} ) ,
Status : states . ObjectReady ,
Dependencies : make ( [ ] addrs . ConfigResource , 0 ) ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingDataSourceSchema ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.const.testing_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "resource_known" ) ,
"value" : cty . StringVal ( "known" ) ,
} ) ) ,
} ,
} ,
PriorStateSrc : nil ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.main[*]" ) ,
PlanApplyable : false , // only deferred changes
PlanComplete : false , // deferred
Action : plans . Create ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "data_unknown" ) ) ,
"resource" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "resource_unknown" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"resource" : nil ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeDeferredResourceInstancePlanned {
ResourceInstancePlanned : stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : stackaddrs . AbsResourceInstanceObject {
Component : stackaddrs . AbsComponentInstance {
Item : stackaddrs . ComponentInstance {
Component : stackaddrs . Component {
Name : "main" ,
} ,
Key : addrs . WildcardKey ,
} ,
} ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : mustAbsResourceInstance ( "data.testing_data_source.data" ) ,
} ,
} ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "data.testing_data_source.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "data.testing_data_source.data" ) ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Read ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "data_unknown" ) ,
"value" : cty . UnknownVal ( cty . String ) ,
} ) ) ,
} ,
ActionReason : plans . ResourceInstanceReadBecauseDependencyPending ,
} ,
PriorStateSrc : nil ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingDataSourceSchema ,
} ,
DeferredReason : providers . DeferredReasonProviderConfigUnknown ,
} ,
& stackplan . PlannedChangeDeferredResourceInstancePlanned {
ResourceInstancePlanned : stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : stackaddrs . AbsResourceInstanceObject {
Component : stackaddrs . AbsComponentInstance {
Item : stackaddrs . ComponentInstance {
Component : stackaddrs . Component {
Name : "main" ,
} ,
Key : addrs . WildcardKey ,
} ,
} ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : mustAbsResourceInstance ( "testing_resource.data" ) ,
} ,
} ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "resource_unknown" ) ,
"value" : cty . UnknownVal ( cty . String ) ,
} ) ) ,
} ,
} ,
PriorStateSrc : nil ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
DeferredReason : providers . DeferredReasonProviderConfigUnknown ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : mustStackInputVariable ( "providers" ) ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
} ,
} ,
} ,
} ,
2025-04-03 04:29:18 -04:00
"removed embedded component duplicate" : {
path : filepath . Join ( "with-single-input" , "removed-component-from-stack-dynamic" ) ,
cycle : TestCycle {
planInputs : map [ string ] cty . Value {
"for_each_input" : cty . MapVal ( map [ string ] cty . Value {
"foo" : cty . StringVal ( "bar" ) ,
} ) ,
"simple_input" : cty . MapVal ( map [ string ] cty . Value {
"foo" : cty . StringVal ( "bar" ) ,
} ) ,
"for_each_removed" : cty . SetVal ( [ ] cty . Value {
cty . StringVal ( "foo" ) ,
} ) ,
"simple_removed" : cty . SetVal ( [ ] cty . Value {
cty . StringVal ( "foo" ) ,
} ) ,
} ,
wantPlannedDiags : initDiags ( func ( diags tfdiags . Diagnostics ) tfdiags . Diagnostics {
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Cannot remove component instance" ,
2025-05-15 02:33:13 -04:00
Detail : "The component instance stack.for_each.component.self[\"foo\"] is targeted by a component block and cannot be removed. The relevant component is defined at git::https://example.com/test.git//with-single-input/for-each-component/for-each-component.tfcomponent.hcl:15,1-17." ,
2025-04-03 04:29:18 -04:00
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : "git::https://example.com/test.git//with-single-input/removed-component-from-stack-dynamic/removed-component-from-stack-dynamic.tfcomponent.hcl" ,
2025-04-03 04:29:18 -04:00
Start : hcl . Pos { Line : 38 , Column : 1 , Byte : 505 } ,
End : hcl . Pos { Line : 38 , Column : 8 , Byte : 512 } ,
} ,
} )
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Cannot remove component instance" ,
2025-05-15 02:33:13 -04:00
Detail : "The component instance stack.simple[\"foo\"].component.self is targeted by a component block and cannot be removed. The relevant component is defined at git::https://example.com/test.git//with-single-input/valid/valid.tfcomponent.hcl:19,1-17." ,
2025-04-03 04:29:18 -04:00
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : "git::https://example.com/test.git//with-single-input/removed-component-from-stack-dynamic/removed-component-from-stack-dynamic.tfcomponent.hcl" ,
2025-04-03 04:29:18 -04:00
Start : hcl . Pos { Line : 60 , Column : 1 , Byte : 811 } ,
End : hcl . Pos { Line : 60 , Column : 8 , Byte : 818 } ,
} ,
} )
return diags
} ) ,
} ,
} ,
2025-04-03 04:40:28 -04:00
"deferred-embedded-stack-update" : {
path : path . Join ( "with-single-input" , "deferred-embedded-stack-for-each" ) ,
state : stackstate . NewStateBuilder ( ) .
AddComponentInstance ( stackstate . NewComponentInstanceBuilder ( mustAbsComponentInstance ( "stack.a[\"deferred\"].component.self" ) ) .
AddInputVariable ( "id" , cty . StringVal ( "deferred" ) ) .
AddInputVariable ( "input" , cty . StringVal ( "deferred" ) ) ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "stack.a[\"deferred\"].component.self.testing_resource.data" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "deferred" ,
"value" : "deferred" ,
} ) ,
} ) ) .
AddInput ( "stacks" , cty . MapVal ( map [ string ] cty . Value {
"deferred" : cty . StringVal ( "deferred" ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "deferred" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "deferred" ) ,
"value" : cty . StringVal ( "deferred" ) ,
} ) ) .
Build ( ) ,
cycle : TestCycle {
planInputs : map [ string ] cty . Value {
"stacks" : cty . UnknownVal ( cty . Map ( cty . String ) ) ,
} ,
wantPlannedChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance . Child ( "a" , addrs . StringKey ( "deferred" ) ) ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
PlanApplyable : false , // Everything is deferred, so nothing to apply.
PlanComplete : false ,
Action : plans . Update ,
PlannedInputValues : map [ string ] plans . DynamicValue {
2025-04-24 03:22:50 -04:00
"id" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "deferred" ) ) ,
2025-04-03 04:40:28 -04:00
"input" : mustPlanDynamicValueDynamicType ( cty . UnknownVal ( cty . String ) ) ,
} ,
PlannedOutputValues : map [ string ] cty . Value { } ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"input" : nil ,
} ,
} ,
& stackplan . PlannedChangeDeferredResourceInstancePlanned {
DeferredReason : providers . DeferredReasonDeferredPrereq ,
ResourceInstancePlanned : stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : stackaddrs . AbsResourceInstanceObject {
Component : stackaddrs . Absolute (
stackaddrs . RootStackInstance . Child ( "a" , addrs . StringKey ( "deferred" ) ) ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
PrevRunAddr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : plans . ChangeSrc {
2025-04-24 03:22:50 -04:00
Action : plans . Update ,
2025-04-03 04:40:28 -04:00
Before : mustPlanDynamicValueSchema ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "deferred" ) ,
"value" : cty . StringVal ( "deferred" ) ,
} ) , stacks_testing_provider . TestingResourceSchema . Body ) ,
After : mustPlanDynamicValueSchema ( cty . ObjectVal ( map [ string ] cty . Value {
2025-04-24 03:22:50 -04:00
"id" : cty . StringVal ( "deferred" ) ,
2025-04-03 04:40:28 -04:00
"value" : cty . UnknownVal ( cty . String ) ,
} ) , stacks_testing_provider . TestingResourceSchema . Body ) ,
AfterSensitivePaths : nil ,
} ,
} ,
PriorStateSrc : & states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "deferred" ,
"value" : "deferred" ,
} ) ,
Dependencies : make ( [ ] addrs . ConfigResource , 0 ) ,
} ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable { Name : "stacks" } ,
Action : plans . Update ,
Before : cty . MapVal ( map [ string ] cty . Value {
"deferred" : cty . StringVal ( "deferred" ) ,
} ) ,
After : cty . UnknownVal ( cty . Map ( cty . String ) ) ,
} ,
} ,
} ,
} ,
"deferred-embedded-stack-create" : {
path : path . Join ( "with-single-input" , "deferred-embedded-stack-for-each" ) ,
cycle : TestCycle {
planInputs : map [ string ] cty . Value {
"stacks" : cty . UnknownVal ( cty . Map ( cty . String ) ) ,
} ,
wantPlannedChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance . Child ( "a" , addrs . WildcardKey ) ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
PlanApplyable : false , // Everything is deferred, so nothing to apply.
PlanComplete : false ,
Action : plans . Create ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . UnknownVal ( cty . String ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . UnknownVal ( cty . String ) ) ,
} ,
PlannedOutputValues : map [ string ] cty . Value { } ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"input" : nil ,
} ,
} ,
& stackplan . PlannedChangeDeferredResourceInstancePlanned {
DeferredReason : providers . DeferredReasonDeferredPrereq ,
ResourceInstancePlanned : stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : stackaddrs . AbsResourceInstanceObject {
Component : stackaddrs . Absolute (
stackaddrs . RootStackInstance . Child ( "a" , addrs . WildcardKey ) ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
PrevRunAddr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValueSchema ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
"value" : cty . UnknownVal ( cty . String ) ,
} ) , stacks_testing_provider . TestingResourceSchema . Body ) ,
AfterSensitivePaths : nil ,
} ,
} ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable { Name : "stacks" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . UnknownVal ( cty . Map ( cty . String ) ) ,
} ,
} ,
} ,
} ,
"deferred-embedded-stack-and-component-for-each" : {
path : path . Join ( "with-single-input" , "deferred-embedded-stack-and-component-for-each" ) ,
cycle : TestCycle {
planInputs : map [ string ] cty . Value {
"stacks" : cty . UnknownVal ( cty . Map ( cty . Set ( cty . String ) ) ) ,
} ,
wantPlannedChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance . Child ( "a" , addrs . WildcardKey ) ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
Key : addrs . WildcardKey ,
} ,
) ,
PlanApplyable : false , // Everything is deferred, so nothing to apply.
PlanComplete : false ,
Action : plans . Create ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . NullVal ( cty . String ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . UnknownVal ( cty . String ) ) ,
} ,
PlannedOutputValues : map [ string ] cty . Value { } ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"input" : nil ,
} ,
} ,
& stackplan . PlannedChangeDeferredResourceInstancePlanned {
DeferredReason : providers . DeferredReasonDeferredPrereq ,
ResourceInstancePlanned : stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : stackaddrs . AbsResourceInstanceObject {
Component : stackaddrs . Absolute (
stackaddrs . RootStackInstance . Child ( "a" , addrs . WildcardKey ) ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
Key : addrs . WildcardKey ,
} ,
) ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
PrevRunAddr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValueSchema ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
"value" : cty . UnknownVal ( cty . String ) ,
} ) , stacks_testing_provider . TestingResourceSchema . Body ) ,
AfterSensitivePaths : nil ,
} ,
} ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable { Name : "stacks" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . UnknownVal ( cty . Map ( cty . Set ( cty . String ) ) ) ,
} ,
} ,
} ,
} ,
2025-04-24 02:56:11 -04:00
"removed block targets stack not in configuration or state" : {
path : filepath . Join ( "with-single-input" , "removed-stack-instance-dynamic" ) ,
cycle : TestCycle {
planInputs : map [ string ] cty . Value {
"input" : cty . MapValEmpty ( cty . String ) ,
"removed" : cty . MapVal ( map [ string ] cty . Value {
"component" : cty . StringVal ( "component" ) ,
} ) ,
} ,
wantPlannedChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable { Name : "input" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . MapValEmpty ( cty . String ) ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable { Name : "removed" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . MapVal ( map [ string ] cty . Value {
"component" : cty . StringVal ( "component" ) ,
} ) ,
} ,
2025-04-24 03:22:50 -04:00
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable { Name : "removed-direct" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . SetValEmpty ( cty . String ) ,
} ,
} ,
} ,
} ,
"embedded stack in state but not in configuration" : {
path : filepath . Join ( "with-single-input" , "valid" ) ,
state : stackstate . NewStateBuilder ( ) .
AddComponentInstance ( stackstate . NewComponentInstanceBuilder ( mustAbsComponentInstance ( "stack.child.component.self" ) ) ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "stack.child.component.self.testing_resource.data" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "leftover" ,
"value" : "leftover" ,
} ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "leftover" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "leftover" ) ,
"value" : cty . StringVal ( "leftover" ) ,
} ) ) .
Build ( ) ,
cycle : TestCycle {
planInputs : map [ string ] cty . Value {
"input" : cty . StringVal ( "input" ) ,
} ,
wantPlannedDiags : initDiags ( func ( diags tfdiags . Diagnostics ) tfdiags . Diagnostics {
return diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Unclaimed component instance" ,
Detail : "The component instance stack.child.component.self is not claimed by any component or removed block in the configuration. Make sure it is instantiated by a component block, or targeted for removal by a removed block." ,
} )
} ) ,
} ,
} ,
"removed and stack block target the same stack" : {
path : filepath . Join ( "with-single-input" , "removed-stack-instance-dynamic" ) ,
cycle : TestCycle {
planInputs : map [ string ] cty . Value {
"input" : cty . MapVal ( map [ string ] cty . Value {
"component" : cty . StringVal ( "component" ) ,
} ) ,
"removed" : cty . MapVal ( map [ string ] cty . Value {
"component" : cty . StringVal ( "component" ) ,
} ) ,
} ,
wantPlannedDiags : initDiags ( func ( diags tfdiags . Diagnostics ) tfdiags . Diagnostics {
return diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Cannot remove stack instance" ,
2025-05-15 02:33:13 -04:00
Detail : "The stack instance stack.simple[\"component\"] is targeted by an embedded stack block and cannot be removed. The relevant embedded stack is defined at git::https://example.com/test.git//with-single-input/removed-stack-instance-dynamic/removed-stack-instance-dynamic.tfcomponent.hcl:25,1-15." ,
2025-04-24 03:22:50 -04:00
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : "git::https://example.com/test.git//with-single-input/removed-stack-instance-dynamic/removed-stack-instance-dynamic.tfcomponent.hcl" ,
2025-04-24 03:22:50 -04:00
Start : hcl . Pos { Line : 36 , Column : 1 , Byte : 441 } ,
End : hcl . Pos { Line : 36 , Column : 8 , Byte : 448 } ,
} ,
} )
} ) ,
} ,
} ,
"removed targets stack block in embedded stack that exists" : {
path : filepath . Join ( "with-single-input" , "removed-stack-from-embedded-stack" ) ,
cycle : TestCycle {
planInputs : map [ string ] cty . Value {
"input" : cty . MapVal ( map [ string ] cty . Value {
"component" : cty . MapVal ( map [ string ] cty . Value {
"component" : cty . StringVal ( "component" ) ,
} ) ,
} ) ,
"removed" : cty . MapVal ( map [ string ] cty . Value {
"component" : cty . MapVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "component" ) ,
"input" : cty . StringVal ( "component" ) ,
} ) ,
} ) ,
} ,
wantPlannedDiags : initDiags ( func ( diags tfdiags . Diagnostics ) tfdiags . Diagnostics {
return diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Cannot remove stack instance" ,
2025-05-15 02:33:13 -04:00
Detail : "The stack instance stack.embedded[\"component\"].stack.simple[\"component\"] is targeted by an embedded stack block and cannot be removed. The relevant embedded stack is defined at git::https://example.com/test.git//with-single-input/removed-stack-instance-dynamic/removed-stack-instance-dynamic.tfcomponent.hcl:25,1-15." ,
2025-04-24 03:22:50 -04:00
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : "git::https://example.com/test.git//with-single-input/removed-stack-from-embedded-stack/removed-stack-from-embedded-stack.tfcomponent.hcl" ,
2025-04-24 03:22:50 -04:00
Start : hcl . Pos { Line : 28 , Column : 1 , Byte : 360 } ,
End : hcl . Pos { Line : 28 , Column : 8 , Byte : 367 } ,
} ,
} )
} ) ,
} ,
} ,
"removed block targets component inside removed stack" : {
path : filepath . Join ( "with-single-input" , "removed-stack-instance-dynamic" ) ,
state : stackstate . NewStateBuilder ( ) .
AddComponentInstance ( stackstate . NewComponentInstanceBuilder ( mustAbsComponentInstance ( "stack.simple[\"component\"].component.self" ) ) .
AddInputVariable ( "id" , cty . StringVal ( "component" ) ) .
AddInputVariable ( "input" , cty . StringVal ( "component" ) ) ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "stack.simple[\"component\"].component.self.testing_resource.data" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "component" ,
"value" : "component" ,
} ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "component" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "component" ) ,
"value" : cty . StringVal ( "component" ) ,
} ) ) .
Build ( ) ,
cycle : TestCycle {
planInputs : map [ string ] cty . Value {
"removed" : cty . MapVal ( map [ string ] cty . Value {
"component" : cty . StringVal ( "component" ) ,
} ) ,
"removed-direct" : cty . SetVal ( [ ] cty . Value {
cty . StringVal ( "component" ) ,
} ) ,
} ,
wantPlannedDiags : initDiags ( func ( diags tfdiags . Diagnostics ) tfdiags . Diagnostics {
return diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Cannot remove component instance" ,
2025-05-15 02:33:13 -04:00
Detail : "The component instance stack.simple[\"component\"].component.self is targeted by a component block and cannot be removed. The relevant component is defined at git::https://example.com/test.git//with-single-input/valid/valid.tfcomponent.hcl:19,1-17." ,
2025-04-24 03:22:50 -04:00
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : "git::https://example.com/test.git//with-single-input/removed-stack-instance-dynamic/removed-stack-instance-dynamic.tfcomponent.hcl" ,
2025-04-24 03:22:50 -04:00
Start : hcl . Pos { Line : 51 , Column : 1 , Byte : 708 } ,
End : hcl . Pos { Line : 51 , Column : 8 , Byte : 715 } ,
} ,
} )
} ) ,
} ,
} ,
"removed block targets orphaned component" : {
path : filepath . Join ( "with-single-input" , "removed-component-from-stack-dynamic" ) ,
state : stackstate . NewStateBuilder ( ) .
AddComponentInstance ( stackstate . NewComponentInstanceBuilder ( mustAbsComponentInstance ( "stack.simple[\"component\"].component.self" ) ) .
AddInputVariable ( "id" , cty . StringVal ( "component" ) ) .
AddInputVariable ( "input" , cty . StringVal ( "component" ) ) ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "stack.simple[\"component\"].component.self.testing_resource.data" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "component" ,
"value" : "component" ,
} ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "component" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "component" ) ,
"value" : cty . StringVal ( "component" ) ,
} ) ) .
Build ( ) ,
cycle : TestCycle {
planInputs : map [ string ] cty . Value {
"simple_input" : cty . MapValEmpty ( cty . String ) ,
"simple_removed" : cty . SetVal ( [ ] cty . Value {
cty . StringVal ( "component" ) ,
} ) ,
} ,
wantPlannedDiags : initDiags ( func ( diags tfdiags . Diagnostics ) tfdiags . Diagnostics {
return diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid removed block" ,
Detail : "The component instance stack.simple[\"component\"].component.self could not be removed. The linked removed block was not executed because the `from` attribute of the removed block targets a component or embedded stack within an orphaned embedded stack.\n\nIn order to remove an entire stack, update your removed block to target the entire removed stack itself instead of the specific elements within it." ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : "git::https://example.com/test.git//with-single-input/removed-component-from-stack-dynamic/removed-component-from-stack-dynamic.tfcomponent.hcl" ,
2025-04-24 03:22:50 -04:00
Start : hcl . Pos { Line : 60 , Column : 1 , Byte : 811 } ,
End : hcl . Pos { Line : 60 , Column : 8 , Byte : 818 } ,
} ,
} )
} ) ,
wantPlannedChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : false ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable { Name : "for_each_input" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . MapValEmpty ( cty . String ) ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable { Name : "for_each_removed" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . SetValEmpty ( cty . String ) ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable { Name : "simple_input" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . MapValEmpty ( cty . String ) ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable { Name : "simple_removed" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . SetVal ( [ ] cty . Value {
cty . StringVal ( "component" ) ,
} ) ,
} ,
} ,
} ,
} ,
"removed block targets orphaned stack" : {
path : filepath . Join ( "with-single-input" , "removed-stack-from-embedded-stack" ) ,
state : stackstate . NewStateBuilder ( ) .
AddComponentInstance ( stackstate . NewComponentInstanceBuilder ( mustAbsComponentInstance ( "stack.embedded[\"component\"].stack.simple[\"component\"].component.self" ) ) .
AddInputVariable ( "id" , cty . StringVal ( "component" ) ) .
AddInputVariable ( "input" , cty . StringVal ( "component" ) ) ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "stack.embedded[\"component\"].stack.simple[\"component\"].component.self.testing_resource.data" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "component" ,
"value" : "component" ,
} ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "component" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "component" ) ,
"value" : cty . StringVal ( "component" ) ,
} ) ) .
Build ( ) ,
cycle : TestCycle {
planInputs : map [ string ] cty . Value {
"input" : cty . MapValEmpty ( cty . Map ( cty . String ) ) ,
"removed" : cty . MapVal ( map [ string ] cty . Value {
"component" : cty . MapVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "component" ) ,
"input" : cty . StringVal ( "component" ) ,
} ) ,
} ) ,
} ,
wantPlannedDiags : initDiags ( func ( diags tfdiags . Diagnostics ) tfdiags . Diagnostics {
return diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid removed block" ,
Detail : "The component instance stack.embedded[\"component\"].stack.simple[\"component\"].component.self could not be removed. The linked removed block was not executed because the `from` attribute of the removed block targets a component or embedded stack within an orphaned embedded stack.\n\nIn order to remove an entire stack, update your removed block to target the entire removed stack itself instead of the specific elements within it." ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : "git::https://example.com/test.git//with-single-input/removed-stack-from-embedded-stack/removed-stack-from-embedded-stack.tfcomponent.hcl" ,
2025-04-24 03:22:50 -04:00
Start : hcl . Pos { Line : 28 , Column : 1 , Byte : 360 } ,
End : hcl . Pos { Line : 28 , Column : 8 , Byte : 367 } ,
} ,
} )
} ) ,
} ,
} ,
"removed block targets orphaned component without config definition" : {
path : filepath . Join ( "with-single-input" , "orphaned-component" ) ,
state : stackstate . NewStateBuilder ( ) .
AddComponentInstance ( stackstate . NewComponentInstanceBuilder ( mustAbsComponentInstance ( "stack.embedded.component.self" ) ) .
AddInputVariable ( "id" , cty . StringVal ( "component" ) ) .
AddInputVariable ( "input" , cty . StringVal ( "component" ) ) ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "stack.embedded.component.self.testing_resource.data" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "component" ,
"value" : "component" ,
} ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "component" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "component" ) ,
"value" : cty . StringVal ( "component" ) ,
} ) ) .
Build ( ) ,
cycle : TestCycle {
wantPlannedDiags : initDiags ( func ( diags tfdiags . Diagnostics ) tfdiags . Diagnostics {
return diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid removed block" ,
Detail : "The component instance stack.embedded.component.self could not be removed. The linked removed block was not executed because the `from` attribute of the removed block targets a component or embedded stack within an orphaned embedded stack.\n\nIn order to remove an entire stack, update your removed block to target the entire removed stack itself instead of the specific elements within it." ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : "git::https://example.com/test.git//with-single-input/orphaned-component/orphaned-component.tfcomponent.hcl" ,
2025-04-24 03:22:50 -04:00
Start : hcl . Pos { Line : 10 , Column : 1 , Byte : 131 } ,
End : hcl . Pos { Line : 10 , Column : 8 , Byte : 138 } ,
} ,
} )
} ) ,
} ,
} ,
"unknown embedded stack with internal component targeted by concrete removed block" : {
path : filepath . Join ( "with-single-input" , "removed-stack-instance-dynamic" ) ,
state : stackstate . NewStateBuilder ( ) .
AddComponentInstance ( stackstate . NewComponentInstanceBuilder ( mustAbsComponentInstance ( "stack.simple[\"component\"].component.self" ) ) .
AddInputVariable ( "id" , cty . StringVal ( "component" ) ) .
AddInputVariable ( "input" , cty . StringVal ( "component" ) ) ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "stack.simple[\"component\"].component.self.testing_resource.data" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "component" ,
"value" : "component" ,
} ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "component" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "component" ) ,
"value" : cty . StringVal ( "component" ) ,
} ) ) .
Build ( ) ,
cycle : TestCycle {
planInputs : map [ string ] cty . Value {
"removed" : cty . UnknownVal ( cty . Map ( cty . String ) ) ,
} ,
wantPlannedChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "stack.simple[\"component\"].component.self" ) ,
Action : plans . Delete ,
Mode : plans . DestroyMode ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . UnknownVal ( cty . String ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . UnknownVal ( cty . String ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"input" : nil ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeDeferredResourceInstancePlanned {
ResourceInstancePlanned : stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "stack.simple[\"component\"].component.self.testing_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Delete ,
Before : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "component" ) ,
"value" : cty . StringVal ( "component" ) ,
} ) ) ,
After : mustPlanDynamicValue ( cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"id" : cty . String ,
"value" : cty . String ,
} ) ) ) ,
} ,
} ,
PriorStateSrc : & states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "component" ,
"value" : "component" ,
} ) ,
Dependencies : make ( [ ] addrs . ConfigResource , 0 ) ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
DeferredReason : providers . DeferredReasonDeferredPrereq ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable { Name : "input" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . MapValEmpty ( cty . String ) ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable { Name : "removed" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . UnknownVal ( cty . Map ( cty . String ) ) ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable { Name : "removed-direct" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . SetValEmpty ( cty . String ) ,
} ,
} ,
} ,
} ,
"remove partial stack" : {
path : filepath . Join ( "with-single-input" , "multiple-components" , "removed" ) ,
state : stackstate . NewStateBuilder ( ) .
AddComponentInstance ( stackstate . NewComponentInstanceBuilder ( mustAbsComponentInstance ( "stack.multiple.component.one" ) ) .
AddInputVariable ( "id" , cty . StringVal ( "one" ) ) .
AddInputVariable ( "input" , cty . StringVal ( "one" ) ) ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "stack.multiple.component.one.testing_resource.data" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "one" ,
"value" : "one" ,
} ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "one" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "one" ) ,
"value" : cty . StringVal ( "one" ) ,
} ) ) .
Build ( ) ,
cycle : TestCycle {
wantPlannedChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "stack.multiple.component.one" ) ,
PlanApplyable : true ,
PlanComplete : true ,
Action : plans . Delete ,
Mode : plans . DestroyMode ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "one" ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "one" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"input" : nil ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "stack.multiple.component.one.testing_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Delete ,
Before : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "one" ) ,
"value" : cty . StringVal ( "one" ) ,
} ) ) ,
After : mustPlanDynamicValue ( cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"id" : cty . String ,
"value" : cty . String ,
} ) ) ) ,
} ,
} ,
PriorStateSrc : & states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "one" ,
"value" : "one" ,
} ) ,
Dependencies : make ( [ ] addrs . ConfigResource , 0 ) ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "stack.multiple.component.two" ) ,
PlanApplyable : true ,
PlanComplete : true ,
Action : plans . Delete ,
Mode : plans . DestroyMode ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlanTimestamp : fakePlanTimestamp ,
} ,
2025-04-24 02:56:11 -04:00
} ,
} ,
} ,
2024-10-09 10:32:54 -04:00
}
for name , tc := range tcs {
t . Run ( name , func ( t * testing . T ) {
ctx := context . Background ( )
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
store := tc . store
if store == nil {
store = stacks_testing_provider . NewResourceStore ( )
}
testContext := TestContext {
timestamp : & fakePlanTimestamp ,
config : loadMainBundleConfigForTest ( t , tc . path ) ,
providers : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
return stacks_testing_provider . NewProviderWithData ( t , store ) , nil
} ,
} ,
dependencyLocks : * lock ,
}
testContext . Plan ( t , ctx , tc . state , tc . cycle )
} )
}
}
2024-02-12 11:24:15 -05:00
func TestPlanWithMissingInputVariable ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "plan-undeclared-variable-in-component" )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1994-09-05T08:50:00Z" )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewBuiltInProvider ( "terraform" ) : func ( ) ( providers . Interface , error ) {
return terraformProvider . NewProvider ( ) , nil
} ,
} ,
ForcePlanTimestamp : & fakePlanTimestamp ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
_ , gotDiags := collectPlanOutput ( changesCh , diagsCh )
// We'll normalize the diagnostics to be of consistent underlying type
// using ForRPC, so that we can easily diff them; we don't actually care
// about which underlying implementation is in use.
gotDiags = gotDiags . ForRPC ( )
var wantDiags tfdiags . Diagnostics
wantDiags = wantDiags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Reference to undeclared input variable" ,
Detail : ` There is no variable "input" block declared in this stack. ` ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "plan-undeclared-variable-in-component/undeclared-variable.tfcomponent.hcl" ) ,
2024-02-12 11:24:15 -05:00
Start : hcl . Pos { Line : 17 , Column : 13 , Byte : 250 } ,
End : hcl . Pos { Line : 17 , Column : 22 , Byte : 259 } ,
} ,
} )
wantDiags = wantDiags . ForRPC ( )
if diff := cmp . Diff ( wantDiags , gotDiags ) ; diff != "" {
t . Errorf ( "wrong diagnostics\n%s" , diff )
}
}
2024-03-04 16:20:17 -05:00
func TestPlanWithNoValueForRequiredVariable ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "plan-no-value-for-required-variable" )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1994-09-05T08:50:00Z" )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewBuiltInProvider ( "terraform" ) : func ( ) ( providers . Interface , error ) {
return terraformProvider . NewProvider ( ) , nil
} ,
} ,
ForcePlanTimestamp : & fakePlanTimestamp ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
_ , gotDiags := collectPlanOutput ( changesCh , diagsCh )
// We'll normalize the diagnostics to be of consistent underlying type
// using ForRPC, so that we can easily diff them; we don't actually care
// about which underlying implementation is in use.
gotDiags = gotDiags . ForRPC ( )
var wantDiags tfdiags . Diagnostics
wantDiags = wantDiags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "No value for required variable" ,
Detail : ` The root input variable "var.beep" is not set, and has no default value. ` ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "plan-no-value-for-required-variable/unset-variable.tfcomponent.hcl" ) ,
2024-03-04 16:20:17 -05:00
Start : hcl . Pos { Line : 1 , Column : 1 , Byte : 0 } ,
End : hcl . Pos { Line : 1 , Column : 16 , Byte : 15 } ,
} ,
} )
wantDiags = wantDiags . ForRPC ( )
if diff := cmp . Diff ( wantDiags , gotDiags ) ; diff != "" {
t . Errorf ( "wrong diagnostics\n%s" , diff )
}
}
2024-03-07 15:49:39 -05:00
func TestPlanWithVariableDefaults ( t * testing . T ) {
// Test that defaults are applied correctly for both unspecified input
// variables and those with an explicit null value.
testCases := map [ string ] struct {
inputs map [ stackaddrs . InputVariable ] ExternalInputValue
} {
"unspecified" : {
inputs : make ( map [ stackaddrs . InputVariable ] ExternalInputValue ) ,
} ,
"explicit null" : {
inputs : map [ stackaddrs . InputVariable ] ExternalInputValue {
2024-06-21 08:05:26 -04:00
{ Name : "beep" } : {
2024-03-07 15:49:39 -05:00
Value : cty . NullVal ( cty . DynamicPseudoType ) ,
2025-05-15 02:33:13 -04:00
DefRange : tfdiags . SourceRange { Filename : "fake.tfcomponent.hcl" } ,
2024-03-07 15:49:39 -05:00
} ,
} ,
} ,
}
for name , tc := range testCases {
t . Run ( name , func ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "plan-variable-defaults" )
2024-06-21 08:05:26 -04:00
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1994-09-05T08:50:00Z" )
if err != nil {
t . Fatal ( err )
}
2024-03-07 15:49:39 -05:00
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
req := PlanRequest {
2024-06-21 08:05:26 -04:00
Config : cfg ,
InputValues : tc . inputs ,
ForcePlanTimestamp : & fakePlanTimestamp ,
2024-03-07 15:49:39 -05:00
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
if len ( diags ) != 0 {
t . Errorf ( "unexpected diagnostics\n%s" , diags . ErrWithWarnings ( ) . Error ( ) )
}
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
2024-07-25 02:04:24 -04:00
Applyable : true ,
2024-03-07 15:49:39 -05:00
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangeOutputValue {
2024-09-16 05:36:36 -04:00
Addr : stackaddrs . OutputValue { Name : "beep" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . StringVal ( "BEEP" ) ,
2024-03-07 15:49:39 -05:00
} ,
& stackplan . PlannedChangeOutputValue {
2024-09-16 05:36:36 -04:00
Addr : stackaddrs . OutputValue { Name : "defaulted" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . StringVal ( "BOOP" ) ,
2024-03-07 15:49:39 -05:00
} ,
& stackplan . PlannedChangeOutputValue {
2024-09-16 05:36:36 -04:00
Addr : stackaddrs . OutputValue { Name : "specified" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . StringVal ( "BEEP" ) ,
2024-03-07 15:49:39 -05:00
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2024-03-07 15:49:39 -05:00
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable {
Name : "beep" ,
} ,
2024-09-16 05:45:19 -04:00
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . StringVal ( "BEEP" ) ,
2024-03-07 15:49:39 -05:00
} ,
}
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
} )
if diff := cmp . Diff ( wantChanges , gotChanges , ctydebug . CmpOptions ) ; diff != "" {
t . Errorf ( "wrong changes\n%s" , diff )
}
} )
}
}
2024-06-18 06:03:43 -04:00
func TestPlanWithComplexVariableDefaults ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , path . Join ( "complex-inputs" ) )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange )
diagsCh := make ( chan tfdiags . Diagnostic )
2024-06-27 10:08:08 -04:00
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-06-18 06:03:43 -04:00
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-06-18 06:03:43 -04:00
} ,
} ,
2024-06-27 10:08:08 -04:00
DependencyLocks : * lock ,
2024-06-18 06:03:43 -04:00
InputValues : map [ stackaddrs . InputVariable ] ExternalInputValue {
2024-06-21 08:05:26 -04:00
{ Name : "optional" } : {
2024-06-18 06:03:43 -04:00
Value : cty . EmptyObjectVal , // This should be populated by defaults.
DefRange : tfdiags . SourceRange { } ,
} ,
} ,
ForcePlanTimestamp : & fakePlanTimestamp ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
changes , diags := collectPlanOutput ( changesCh , diagsCh )
if len ( diags ) != 0 {
t . Fatalf ( "unexpected diagnostics: %s" , diags )
}
sort . SliceStable ( changes , func ( i , j int ) bool {
return plannedChangeSortKey ( changes [ i ] ) < plannedChangeSortKey ( changes [ j ] )
} )
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.self" ) ,
PlanComplete : true ,
PlanApplyable : true ,
Action : plans . Create ,
RequiredComponents : collections . NewSet [ stackaddrs . AbsComponent ] ( ) ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"input" : mustPlanDynamicValueDynamicType ( cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "cec9bc39" ) ,
"value" : cty . StringVal ( "hello, mercury!" ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "78d8b3d7" ) ,
"value" : cty . StringVal ( "hello, venus!" ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . NullVal ( cty . String ) ,
"value" : cty . StringVal ( "hello, earth!" ) ,
} ) ,
} ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"input" : nil ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.self.testing_resource.data[0]" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data[0]" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data[0]" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "cec9bc39" ) ,
"value" : cty . StringVal ( "hello, mercury!" ) ,
} ) ) ,
} ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.self.testing_resource.data[1]" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data[1]" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data[1]" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "78d8b3d7" ) ,
"value" : cty . StringVal ( "hello, venus!" ) ,
} ) ) ,
} ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.self.testing_resource.data[2]" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data[2]" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data[2]" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
"value" : cty . StringVal ( "hello, earth!" ) ,
} ) ) ,
} ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2024-06-18 06:03:43 -04:00
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "stack.child.component.parent" ) ,
PlanComplete : true ,
PlanApplyable : true ,
Action : plans . Create ,
RequiredComponents : collections . NewSet [ stackaddrs . AbsComponent ] ( ) ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"input" : mustPlanDynamicValueDynamicType ( cty . ListVal ( [ ] cty . Value {
cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "cec9bc39" ) ,
"value" : cty . StringVal ( "hello, mercury!" ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "78d8b3d7" ) ,
"value" : cty . StringVal ( "hello, venus!" ) ,
} ) ,
cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . NullVal ( cty . String ) ,
"value" : cty . StringVal ( "hello, earth!" ) ,
} ) ,
} ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"input" : nil ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "stack.child.component.parent.testing_resource.data[0]" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data[0]" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data[0]" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "cec9bc39" ) ,
"value" : cty . StringVal ( "hello, mercury!" ) ,
} ) ) ,
} ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "stack.child.component.parent.testing_resource.data[1]" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data[1]" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data[1]" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "78d8b3d7" ) ,
"value" : cty . StringVal ( "hello, venus!" ) ,
} ) ) ,
} ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "stack.child.component.parent.testing_resource.data[2]" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data[2]" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data[2]" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
"value" : cty . StringVal ( "hello, earth!" ) ,
} ) ) ,
} ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "default" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . ObjectVal ( map [ string ] cty . Value {
2024-06-18 06:03:43 -04:00
"id" : cty . StringVal ( "cec9bc39" ) ,
"value" : cty . StringVal ( "hello, mercury!" ) ,
} ) ,
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "optional" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . ObjectVal ( map [ string ] cty . Value {
2024-06-18 06:03:43 -04:00
"id" : cty . NullVal ( cty . String ) ,
"value" : cty . StringVal ( "hello, earth!" ) ,
} ) ,
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "optional_default" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . ObjectVal ( map [ string ] cty . Value {
2024-06-18 06:03:43 -04:00
"id" : cty . StringVal ( "78d8b3d7" ) ,
"value" : cty . StringVal ( "hello, venus!" ) ,
} ) ,
} ,
}
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , changes , changesCmpOpts ) ; diff != "" {
2024-06-18 06:03:43 -04:00
t . Errorf ( "wrong changes\n%s" , diff )
}
}
2023-07-25 19:23:42 -04:00
func TestPlanWithSingleResource ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "with-single-resource" )
2023-10-04 18:35:26 -04:00
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1994-09-05T08:50:00Z" )
if err != nil {
t . Fatal ( err )
}
2023-07-25 19:23:42 -04:00
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
req := PlanRequest {
Config : cfg ,
2023-08-30 20:13:16 -04:00
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewBuiltInProvider ( "terraform" ) : func ( ) ( providers . Interface , error ) {
return terraformProvider . NewProvider ( ) , nil
} ,
} ,
2023-10-04 18:35:26 -04:00
ForcePlanTimestamp : & fakePlanTimestamp ,
2023-07-25 19:23:42 -04:00
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
if len ( diags ) != 0 {
t . Errorf ( "unexpected diagnostics\n%s" , diags . ErrWithWarnings ( ) . Error ( ) )
}
2023-07-25 20:16:13 -04:00
// The order of emission for our planned changes is unspecified since it
// depends on how the various goroutines get scheduled, and so we'll
// arbitrarily sort gotChanges lexically by the name of the change type
// so that we have some dependable order to diff against below.
sort . Slice ( gotChanges , func ( i , j int ) bool {
ic := gotChanges [ i ]
jc := gotChanges [ j ]
return fmt . Sprintf ( "%T" , ic ) < fmt . Sprintf ( "%T" , jc )
} )
2023-07-25 19:23:42 -04:00
wantChanges := [ ] stackplan . PlannedChange {
2023-07-25 20:16:13 -04:00
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
2023-07-26 11:04:13 -04:00
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
2024-03-01 20:16:14 -05:00
Action : plans . Create ,
PlanApplyable : true ,
PlanComplete : true ,
PlannedCheckResults : & states . CheckResults { } ,
PlannedInputValues : make ( map [ string ] plans . DynamicValue ) ,
2023-12-08 21:52:24 -05:00
PlannedOutputValues : map [ string ] cty . Value {
"input" : cty . StringVal ( "hello" ) ,
"output" : cty . UnknownVal ( cty . String ) ,
} ,
PlanTimestamp : fakePlanTimestamp ,
2023-07-26 11:04:13 -04:00
} ,
2023-07-25 19:23:42 -04:00
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
2023-07-25 20:16:13 -04:00
& stackplan . PlannedChangeOutputValue {
2024-09-16 05:36:36 -04:00
Addr : stackaddrs . OutputValue { Name : "obj" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . ObjectVal ( map [ string ] cty . Value {
2023-07-25 20:16:13 -04:00
"input" : cty . StringVal ( "hello" ) ,
"output" : cty . UnknownVal ( cty . String ) ,
2024-09-16 05:36:36 -04:00
} ) ,
2023-07-25 20:16:13 -04:00
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2023-07-25 19:23:42 -04:00
& stackplan . PlannedChangeResourceInstancePlanned {
2023-10-23 20:57:45 -04:00
ResourceInstanceObjectAddr : stackaddrs . AbsResourceInstanceObject {
Component : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "terraform_data" ,
Name : "main" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
2023-07-25 19:23:42 -04:00
} ,
2023-10-23 20:57:45 -04:00
} ,
2023-10-24 14:03:41 -04:00
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "terraform.io/builtin/terraform" ) ,
} ,
2023-07-25 19:23:42 -04:00
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "terraform_data" ,
Name : "main" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
PrevRunAddr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "terraform_data" ,
Name : "main" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . NewBuiltInProvider ( "terraform" ) ,
} ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
2023-07-25 20:16:13 -04:00
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
2023-07-25 19:23:42 -04:00
After : plans . DynamicValue {
2023-07-25 20:16:13 -04:00
// This is an object conforming to the terraform_data
// resource type's schema.
//
// FIXME: Should write this a different way that is
// scrutable and won't break each time something gets
// added to the terraform_data schema. (We can't use
// mustPlanDynamicValue here because the resource type
// uses DynamicPseudoType attributes, which require
// explicitly-typed encoding.)
0x84 , 0xa2 , 0x69 , 0x64 , 0xc7 , 0x03 , 0x0c , 0x81 ,
0x01 , 0xc2 , 0xa5 , 0x69 , 0x6e , 0x70 , 0x75 , 0x74 ,
0x92 , 0xc4 , 0x08 , 0x22 , 0x73 , 0x74 , 0x72 , 0x69 ,
0x6e , 0x67 , 0x22 , 0xa5 , 0x68 , 0x65 , 0x6c , 0x6c ,
0x6f , 0xa6 , 0x6f , 0x75 , 0x74 , 0x70 , 0x75 , 0x74 ,
0x92 , 0xc4 , 0x08 , 0x22 , 0x73 , 0x74 , 0x72 , 0x69 ,
0x6e , 0x67 , 0x22 , 0xd4 , 0x00 , 0x00 , 0xb0 , 0x74 ,
0x72 , 0x69 , 0x67 , 0x67 , 0x65 , 0x72 , 0x73 , 0x5f ,
0x72 , 0x65 , 0x70 , 0x6c , 0x61 , 0x63 , 0x65 , 0xc0 ,
2023-07-25 19:23:42 -04:00
} ,
} ,
} ,
2023-10-04 18:35:26 -04:00
// The following is schema for the real terraform_data resource
// type from the real terraform.io/builtin/terraform provider
// maintained elsewhere in this codebase. If that schema changes
// in future then this should change to match it.
2025-03-11 15:58:44 -04:00
Schema : providers . Schema {
Body : & configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"input" : { Type : cty . DynamicPseudoType , Optional : true } ,
"output" : { Type : cty . DynamicPseudoType , Computed : true } ,
"triggers_replace" : { Type : cty . DynamicPseudoType , Optional : true } ,
"id" : { Type : cty . String , Computed : true } ,
} ,
} ,
Identity : & configschema . Object {
Attributes : map [ string ] * configschema . Attribute {
"id" : {
Type : cty . String ,
Description : "The unique identifier for the data store." ,
Required : true ,
} ,
} ,
Nesting : configschema . NestingSingle ,
2023-10-04 18:35:26 -04:00
} ,
} ,
2023-07-25 19:23:42 -04:00
} ,
}
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , gotChanges , changesCmpOpts ) ; diff != "" {
2023-07-25 19:23:42 -04:00
t . Errorf ( "wrong changes\n%s" , diff )
}
}
2024-06-12 21:47:31 -04:00
func TestPlanWithEphemeralInputVariables ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "variable-ephemeral" )
t . Run ( "with variables set" , func ( t * testing . T ) {
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
2024-06-21 08:05:26 -04:00
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1994-09-05T08:50:00Z" )
if err != nil {
t . Fatal ( err )
}
2024-06-12 21:47:31 -04:00
req := PlanRequest {
Config : cfg ,
InputValues : map [ stackaddrs . InputVariable ] stackeval . ExternalInputValue {
{ Name : "eph" } : { Value : cty . StringVal ( "eph value" ) } ,
{ Name : "noneph" } : { Value : cty . StringVal ( "noneph value" ) } ,
} ,
2024-06-21 08:05:26 -04:00
ForcePlanTimestamp : & fakePlanTimestamp ,
2024-06-12 21:47:31 -04:00
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
if len ( diags ) != 0 {
t . Errorf ( "unexpected diagnostics\n%s" , diags . ErrWithWarnings ( ) . Error ( ) )
}
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
2024-07-25 02:04:24 -04:00
Applyable : true ,
2024-06-12 21:47:31 -04:00
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2024-06-12 21:47:31 -04:00
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable {
Name : "eph" ,
} ,
2024-09-16 05:45:19 -04:00
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
2024-10-08 10:46:31 -04:00
After : cty . NullVal ( cty . String ) , // ephemeral
2024-06-12 21:47:31 -04:00
RequiredOnApply : true ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable {
Name : "noneph" ,
} ,
2024-09-16 05:45:19 -04:00
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . StringVal ( "noneph value" ) ,
2024-06-12 21:47:31 -04:00
} ,
}
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
} )
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , gotChanges , changesCmpOpts ) ; diff != "" {
2024-06-12 21:47:31 -04:00
t . Errorf ( "wrong changes\n%s" , diff )
}
} )
t . Run ( "without variables set" , func ( t * testing . T ) {
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
2024-06-21 08:05:26 -04:00
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1994-09-05T08:50:00Z" )
if err != nil {
t . Fatal ( err )
}
2024-06-12 21:47:31 -04:00
req := PlanRequest {
InputValues : map [ stackaddrs . InputVariable ] stackeval . ExternalInputValue {
// Intentionally not set for this subtest.
} ,
2025-03-18 04:28:31 -04:00
Config : cfg ,
2024-06-21 08:05:26 -04:00
ForcePlanTimestamp : & fakePlanTimestamp ,
2024-06-12 21:47:31 -04:00
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
if len ( diags ) != 0 {
t . Errorf ( "unexpected diagnostics\n%s" , diags . ErrWithWarnings ( ) . Error ( ) )
}
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
2024-07-25 02:04:24 -04:00
Applyable : true ,
2024-06-12 21:47:31 -04:00
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2024-06-12 21:47:31 -04:00
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable {
Name : "eph" ,
} ,
2024-09-16 05:45:19 -04:00
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
2024-10-08 10:46:31 -04:00
After : cty . NullVal ( cty . String ) , // ephemeral
2024-06-12 21:47:31 -04:00
RequiredOnApply : false ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable {
Name : "noneph" ,
} ,
2024-09-16 05:45:19 -04:00
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . NullVal ( cty . String ) ,
2024-06-12 21:47:31 -04:00
} ,
}
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
} )
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , gotChanges , changesCmpOpts ) ; diff != "" {
2024-06-12 21:47:31 -04:00
t . Errorf ( "wrong changes\n%s" , diff )
}
} )
}
2023-07-21 20:13:35 -04:00
func TestPlanVariableOutputRoundtripNested ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "variable-output-roundtrip-nested" )
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
2024-06-21 08:05:26 -04:00
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1994-09-05T08:50:00Z" )
if err != nil {
t . Fatal ( err )
}
2023-07-21 20:13:35 -04:00
req := PlanRequest {
2024-06-21 08:05:26 -04:00
Config : cfg ,
ForcePlanTimestamp : & fakePlanTimestamp ,
2023-07-21 20:13:35 -04:00
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
if len ( diags ) != 0 {
t . Errorf ( "unexpected diagnostics\n%s" , diags . ErrWithWarnings ( ) . Error ( ) )
}
wantChanges := [ ] stackplan . PlannedChange {
2023-10-04 18:35:26 -04:00
& stackplan . PlannedChangeApplyable {
2024-07-25 02:04:24 -04:00
Applyable : true ,
2023-10-04 18:35:26 -04:00
} ,
2023-07-21 20:13:35 -04:00
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangeOutputValue {
2024-09-16 05:36:36 -04:00
Addr : stackaddrs . OutputValue { Name : "msg" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . StringVal ( "default" ) ,
2023-07-21 20:13:35 -04:00
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2023-10-04 18:35:26 -04:00
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable {
Name : "msg" ,
} ,
2024-09-16 05:45:19 -04:00
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . StringVal ( "default" ) ,
2023-07-21 20:13:35 -04:00
} ,
}
2023-10-04 18:35:26 -04:00
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
2024-02-21 04:40:20 -05:00
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
2023-10-04 18:35:26 -04:00
} )
2023-07-21 20:13:35 -04:00
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , gotChanges , changesCmpOpts ) ; diff != "" {
2023-07-21 20:13:35 -04:00
t . Errorf ( "wrong changes\n%s" , diff )
}
}
2023-12-13 19:38:22 -05:00
func TestPlanSensitiveOutput ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "sensitive-output" )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
req := PlanRequest {
Config : cfg ,
ForcePlanTimestamp : & fakePlanTimestamp ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
if len ( diags ) != 0 {
t . Errorf ( "unexpected diagnostics\n%s" , diags . ErrWithWarnings ( ) . Error ( ) )
}
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
2024-03-01 20:16:14 -05:00
Action : plans . Create ,
PlanApplyable : true ,
PlanComplete : true ,
PlannedCheckResults : & states . CheckResults { } ,
PlannedInputValues : make ( map [ string ] plans . DynamicValue ) ,
2023-12-13 19:38:22 -05:00
PlannedOutputValues : map [ string ] cty . Value {
"out" : cty . StringVal ( "secret" ) . Mark ( marks . Sensitive ) ,
} ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangeOutputValue {
2024-09-16 05:36:36 -04:00
Addr : stackaddrs . OutputValue { Name : "result" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . StringVal ( "secret" ) . Mark ( marks . Sensitive ) ,
2023-12-13 19:38:22 -05:00
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2023-12-13 19:38:22 -05:00
}
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
2024-02-21 04:40:20 -05:00
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
2023-12-13 19:38:22 -05:00
} )
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , gotChanges , changesCmpOpts ) ; diff != "" {
2023-12-13 19:38:22 -05:00
t . Errorf ( "wrong changes\n%s" , diff )
}
}
func TestPlanSensitiveOutputNested ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "sensitive-output-nested" )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
req := PlanRequest {
Config : cfg ,
ForcePlanTimestamp : & fakePlanTimestamp ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
if len ( diags ) != 0 {
t . Errorf ( "unexpected diagnostics\n%s" , diags . ErrWithWarnings ( ) . Error ( ) )
}
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
2024-02-21 04:40:20 -05:00
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangeOutputValue {
2024-09-16 05:36:36 -04:00
Addr : stackaddrs . OutputValue { Name : "result" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . StringVal ( "secret" ) . Mark ( marks . Sensitive ) ,
2024-02-21 04:40:20 -05:00
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2023-12-13 19:38:22 -05:00
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance . Child ( "child" , addrs . NoKey ) ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
2024-03-01 20:16:14 -05:00
Action : plans . Create ,
PlanApplyable : true ,
PlanComplete : true ,
PlannedCheckResults : & states . CheckResults { } ,
PlannedInputValues : make ( map [ string ] plans . DynamicValue ) ,
2023-12-13 19:38:22 -05:00
PlannedOutputValues : map [ string ] cty . Value {
"out" : cty . StringVal ( "secret" ) . Mark ( marks . Sensitive ) ,
} ,
PlanTimestamp : fakePlanTimestamp ,
} ,
}
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
2024-02-21 04:40:20 -05:00
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
2023-12-13 19:38:22 -05:00
} )
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , gotChanges , changesCmpOpts ) ; diff != "" {
2023-12-13 19:38:22 -05:00
t . Errorf ( "wrong changes\n%s" , diff )
}
}
2023-12-14 16:00:18 -05:00
func TestPlanSensitiveOutputAsInput ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "sensitive-output-as-input" )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
req := PlanRequest {
Config : cfg ,
ForcePlanTimestamp : & fakePlanTimestamp ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
if len ( diags ) != 0 {
t . Errorf ( "unexpected diagnostics\n%s" , diags . ErrWithWarnings ( ) . Error ( ) )
}
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
2024-08-12 09:02:36 -04:00
Action : plans . Create ,
PlanApplyable : true ,
PlanComplete : true ,
RequiredComponents : collections . NewSet [ stackaddrs . AbsComponent ] (
mustAbsComponent ( "stack.sensitive.component.self" ) ,
) ,
2024-03-01 20:16:14 -05:00
PlannedCheckResults : & states . CheckResults { } ,
2023-12-14 16:00:18 -05:00
PlannedInputValues : map [ string ] plans . DynamicValue {
"secret" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "secret" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"secret" : {
{
Marks : cty . NewValueMarks ( marks . Sensitive ) ,
} ,
} ,
} ,
PlannedOutputValues : map [ string ] cty . Value {
"result" : cty . StringVal ( "SECRET" ) . Mark ( marks . Sensitive ) ,
} ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangeOutputValue {
2024-09-16 05:36:36 -04:00
Addr : stackaddrs . OutputValue { Name : "result" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) , // MessagePack nil
After : cty . StringVal ( "SECRET" ) . Mark ( marks . Sensitive ) ,
2023-12-14 16:00:18 -05:00
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2024-02-21 04:40:20 -05:00
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance . Child ( "sensitive" , addrs . NoKey ) ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
2024-03-01 20:16:14 -05:00
Action : plans . Create ,
PlanApplyable : true ,
PlanComplete : true ,
PlannedCheckResults : & states . CheckResults { } ,
PlannedInputValues : make ( map [ string ] plans . DynamicValue ) ,
2024-02-21 04:40:20 -05:00
PlannedOutputValues : map [ string ] cty . Value {
"out" : cty . StringVal ( "secret" ) . Mark ( marks . Sensitive ) ,
} ,
PlanTimestamp : fakePlanTimestamp ,
} ,
2023-12-14 16:00:18 -05:00
}
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
2024-02-21 04:40:20 -05:00
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
2023-12-14 16:00:18 -05:00
} )
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , gotChanges , changesCmpOpts ) ; diff != "" {
2023-12-14 16:00:18 -05:00
t . Errorf ( "wrong changes\n%s" , diff )
}
}
2023-08-22 21:31:10 -04:00
func TestPlanWithProviderConfig ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "with-provider-config" )
providerAddr := addrs . MustParseProviderSourceString ( "example.com/test/test" )
providerSchema := & providers . GetProviderSchemaResponse {
Provider : providers . Schema {
2025-03-04 10:33:43 -05:00
Body : & configschema . Block {
2023-08-22 21:31:10 -04:00
Attributes : map [ string ] * configschema . Attribute {
"name" : {
Type : cty . String ,
Required : true ,
} ,
} ,
} ,
} ,
}
inputVarAddr := stackaddrs . InputVariable { Name : "name" }
fakeSrcRng := tfdiags . SourceRange {
Filename : "fake-source" ,
}
2024-06-27 10:08:08 -04:00
lock := depsfile . NewLocks ( )
lock . SetProvider (
providerAddr ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2023-08-22 21:31:10 -04:00
t . Run ( "valid" , func ( t * testing . T ) {
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
2024-02-16 04:46:50 -05:00
provider := & default_testing_provider . MockProvider {
2023-08-22 21:31:10 -04:00
GetProviderSchemaResponse : providerSchema ,
ValidateProviderConfigResponse : & providers . ValidateProviderConfigResponse { } ,
ConfigureProviderResponse : & providers . ConfigureProviderResponse { } ,
}
req := PlanRequest {
Config : cfg ,
InputValues : map [ stackaddrs . InputVariable ] ExternalInputValue {
inputVarAddr : {
Value : cty . StringVal ( "Jackson" ) ,
DefRange : fakeSrcRng ,
} ,
} ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
providerAddr : func ( ) ( providers . Interface , error ) {
return provider , nil
} ,
} ,
2024-06-27 10:08:08 -04:00
DependencyLocks : * lock ,
2023-08-22 21:31:10 -04:00
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
_ , diags := collectPlanOutput ( changesCh , diagsCh )
if len ( diags ) != 0 {
t . Errorf ( "unexpected diagnostics\n%s" , diags . ErrWithWarnings ( ) . Error ( ) )
}
if ! provider . ValidateProviderConfigCalled {
t . Error ( "ValidateProviderConfig wasn't called" )
} else {
req := provider . ValidateProviderConfigRequest
if got , want := req . Config . GetAttr ( "name" ) , cty . StringVal ( "Jackson" ) ; ! got . RawEquals ( want ) {
t . Errorf ( "wrong name in ValidateProviderConfig\ngot: %#v\nwant: %#v" , got , want )
}
}
if ! provider . ConfigureProviderCalled {
t . Error ( "ConfigureProvider wasn't called" )
} else {
req := provider . ConfigureProviderRequest
if got , want := req . Config . GetAttr ( "name" ) , cty . StringVal ( "Jackson" ) ; ! got . RawEquals ( want ) {
t . Errorf ( "wrong name in ConfigureProvider\ngot: %#v\nwant: %#v" , got , want )
}
}
if ! provider . CloseCalled {
t . Error ( "provider wasn't closed" )
}
} )
}
2024-02-16 04:46:50 -05:00
2024-02-15 04:45:47 -05:00
func TestPlanWithRemovedResource ( t * testing . T ) {
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1994-09-05T08:50:00Z" )
if err != nil {
t . Fatal ( err )
}
attrs := map [ string ] interface { } {
"id" : "FE1D5830765C" ,
"input" : map [ string ] interface { } {
"value" : "hello" ,
"type" : "string" ,
} ,
"output" : map [ string ] interface { } {
"value" : nil ,
"type" : "string" ,
} ,
"triggers_replace" : nil ,
}
attrsJSON , err := json . Marshal ( attrs )
if err != nil {
t . Fatal ( err )
}
// We want to see that it's adding the extra context for when a provider is
// missing for a resource that's in state and not in config.
expectedDiagnostic := "has resources in state that"
tcs := make ( map [ string ] * string )
tcs [ "missing-providers" ] = & expectedDiagnostic
tcs [ "valid-providers" ] = nil
for name , diag := range tcs {
t . Run ( name , func ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , path . Join ( "empty-component" , name ) )
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewBuiltInProvider ( "terraform" ) : func ( ) ( providers . Interface , error ) {
return terraformProvider . NewProvider ( ) , nil
} ,
} ,
ForcePlanTimestamp : & fakePlanTimestamp ,
// PrevState specifies a state with a resource that is not present in
// the current configuration. This is a common situation when a resource
// is removed from the configuration but still exists in the state.
PrevState : stackstate . NewStateBuilder ( ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( stackaddrs . AbsResourceInstanceObject {
Component : stackaddrs . AbsComponentInstance {
Stack : stackaddrs . RootStackInstance ,
Item : stackaddrs . ComponentInstance {
Component : stackaddrs . Component {
Name : "self" ,
} ,
Key : addrs . NoKey ,
} ,
} ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : addrs . AbsResourceInstance {
Module : addrs . RootModuleInstance ,
Resource : addrs . ResourceInstance {
Resource : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "terraform_data" ,
Name : "main" ,
} ,
Key : addrs . NoKey ,
} ,
} ,
DeposedKey : addrs . NotDeposed ,
} ,
} ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
SchemaVersion : 0 ,
AttrsJSON : attrsJSON ,
Status : states . ObjectReady ,
} ) .
SetProviderAddr ( addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "terraform.io/builtin/terraform" ) ,
} ) ) .
Build ( ) ,
}
changesCh := make ( chan stackplan . PlannedChange )
diagsCh := make ( chan tfdiags . Diagnostic )
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
_ , diags := collectPlanOutput ( changesCh , diagsCh )
if diag != nil {
if len ( diags ) == 0 {
t . Fatalf ( "expected diagnostics, got none" )
}
if ! strings . Contains ( diags [ 0 ] . Description ( ) . Detail , * diag ) {
t . Fatalf ( "expected diagnostic %q, got %q" , * diag , diags [ 0 ] . Description ( ) . Detail )
}
} else if len ( diags ) > 0 {
t . Fatalf ( "unexpected diagnostics: %s" , diags . ErrWithWarnings ( ) . Error ( ) )
}
} )
}
}
2023-08-22 21:31:10 -04:00
2024-02-16 04:46:50 -05:00
func TestPlanWithSensitivePropagation ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , path . Join ( "with-single-input" , "sensitive-input" ) )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
2024-06-27 10:08:08 -04:00
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-02-16 04:46:50 -05:00
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-02-16 04:46:50 -05:00
} ,
} ,
2024-06-27 10:08:08 -04:00
DependencyLocks : * lock ,
2024-02-16 04:46:50 -05:00
ForcePlanTimestamp : & fakePlanTimestamp ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
if len ( diags ) != 0 {
t . Errorf ( "unexpected diagnostics\n%s" , diags . ErrWithWarnings ( ) . Error ( ) )
}
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
PlanApplyable : true ,
PlanComplete : true ,
Action : plans . Create ,
RequiredComponents : collections . NewSet [ stackaddrs . AbsComponent ] (
stackaddrs . AbsComponent {
Stack : stackaddrs . RootStackInstance ,
Item : stackaddrs . Component { Name : "sensitive" } ,
} ,
) ,
2024-03-01 20:16:14 -05:00
PlannedCheckResults : & states . CheckResults { } ,
2024-02-16 04:46:50 -05:00
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . NullVal ( cty . String ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "secret" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"input" : {
{
Marks : cty . NewValueMarks ( marks . Sensitive ) ,
} ,
} ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : stackaddrs . AbsResourceInstanceObject {
Component : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
PrevRunAddr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . NewDefaultProvider ( "testing" ) ,
} ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValueSchema ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
"value" : cty . StringVal ( "secret" ) ,
2025-03-11 15:58:44 -04:00
} ) , stacks_testing_provider . TestingResourceSchema . Body ) ,
2024-04-16 20:20:33 -04:00
AfterSensitivePaths : [ ] cty . Path {
cty . GetAttrPath ( "value" ) ,
2024-02-16 04:46:50 -05:00
} ,
} ,
} ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "sensitive" } ,
} ,
) ,
2024-03-01 20:16:14 -05:00
PlanApplyable : true ,
PlanComplete : true ,
Action : plans . Create ,
PlannedCheckResults : & states . CheckResults { } ,
PlannedInputValues : make ( map [ string ] plans . DynamicValue ) ,
2024-02-16 04:46:50 -05:00
PlannedOutputValues : map [ string ] cty . Value {
"out" : cty . StringVal ( "secret" ) . Mark ( marks . Sensitive ) ,
} ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2024-02-16 04:46:50 -05:00
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "id" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . NullVal ( cty . String ) ,
2024-02-16 04:46:50 -05:00
} ,
}
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
} )
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , gotChanges , changesCmpOpts ) ; diff != "" {
2024-02-16 04:46:50 -05:00
t . Errorf ( "wrong changes\n%s" , diff )
}
}
func TestPlanWithSensitivePropagationNested ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , path . Join ( "with-single-input" , "sensitive-input-nested" ) )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
2024-06-27 10:08:08 -04:00
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-02-16 04:46:50 -05:00
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-02-16 04:46:50 -05:00
} ,
} ,
2024-06-27 10:08:08 -04:00
DependencyLocks : * lock ,
2024-02-16 04:46:50 -05:00
ForcePlanTimestamp : & fakePlanTimestamp ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
if len ( diags ) != 0 {
t . Errorf ( "unexpected diagnostics\n%s" , diags . ErrWithWarnings ( ) . Error ( ) )
}
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
2024-08-12 09:02:36 -04:00
Action : plans . Create ,
PlanApplyable : true ,
PlanComplete : true ,
RequiredComponents : collections . NewSet [ stackaddrs . AbsComponent ] (
mustAbsComponent ( "stack.sensitive.component.self" ) ,
) ,
2024-03-01 20:16:14 -05:00
PlannedCheckResults : & states . CheckResults { } ,
2024-02-16 04:46:50 -05:00
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . NullVal ( cty . String ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "secret" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"input" : {
{
Marks : cty . NewValueMarks ( marks . Sensitive ) ,
} ,
} ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : stackaddrs . AbsResourceInstanceObject {
Component : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
PrevRunAddr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . NewDefaultProvider ( "testing" ) ,
} ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValueSchema ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
"value" : cty . StringVal ( "secret" ) ,
2025-03-11 15:58:44 -04:00
} ) , stacks_testing_provider . TestingResourceSchema . Body ) ,
2024-04-16 20:20:33 -04:00
AfterSensitivePaths : [ ] cty . Path {
cty . GetAttrPath ( "value" ) ,
2024-02-16 04:46:50 -05:00
} ,
} ,
} ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2024-02-16 04:46:50 -05:00
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance . Child ( "sensitive" , addrs . NoKey ) ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
2024-03-01 20:16:14 -05:00
Action : plans . Create ,
PlanApplyable : true ,
PlanComplete : true ,
PlannedCheckResults : & states . CheckResults { } ,
PlannedInputValues : make ( map [ string ] plans . DynamicValue ) ,
2024-02-16 04:46:50 -05:00
PlannedOutputValues : map [ string ] cty . Value {
"out" : cty . StringVal ( "secret" ) . Mark ( marks . Sensitive ) ,
} ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "id" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . NullVal ( cty . String ) ,
2024-02-16 04:46:50 -05:00
} ,
}
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
} )
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , gotChanges , changesCmpOpts ) ; diff != "" {
2024-02-16 04:46:50 -05:00
t . Errorf ( "wrong changes\n%s" , diff )
}
}
2024-02-26 05:36:19 -05:00
func TestPlanWithForEach ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , path . Join ( "with-single-input" , "input-from-component-list" ) )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
2024-06-27 10:08:08 -04:00
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-02-26 05:36:19 -05:00
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-02-26 05:36:19 -05:00
} ,
} ,
2024-06-27 10:08:08 -04:00
DependencyLocks : * lock ,
2024-02-26 05:36:19 -05:00
ForcePlanTimestamp : & fakePlanTimestamp ,
InputValues : map [ stackaddrs . InputVariable ] ExternalInputValue {
2024-06-21 08:05:26 -04:00
{ Name : "components" } : {
2024-02-26 05:36:19 -05:00
Value : cty . ListVal ( [ ] cty . Value { cty . StringVal ( "one" ) , cty . StringVal ( "two" ) , cty . StringVal ( "three" ) } ) ,
DefRange : tfdiags . SourceRange { } ,
} ,
} ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
_ , diags := collectPlanOutput ( changesCh , diagsCh )
reportDiagnosticsForTest ( t , diags )
if len ( diags ) != 0 {
t . FailNow ( ) // We reported the diags above/
}
}
2024-03-01 20:16:14 -05:00
func TestPlanWithCheckableObjects ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "checkable-objects" )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1994-09-05T08:50:00Z" )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
2024-06-27 10:08:08 -04:00
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-03-01 20:16:14 -05:00
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-03-01 20:16:14 -05:00
} ,
} ,
2024-06-27 10:08:08 -04:00
DependencyLocks : * lock ,
2024-03-01 20:16:14 -05:00
ForcePlanTimestamp : & fakePlanTimestamp ,
InputValues : map [ stackaddrs . InputVariable ] ExternalInputValue {
2024-06-21 08:05:26 -04:00
{ Name : "foo" } : {
2024-03-01 20:16:14 -05:00
Value : cty . StringVal ( "bar" ) ,
} ,
} ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
var wantDiags tfdiags . Diagnostics
wantDiags = wantDiags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagWarning ,
Summary : "Check block assertion failed" ,
Detail : ` value must be 'baz' ` ,
Subject : & hcl . Range {
Filename : mainBundleSourceAddrStr ( "checkable-objects/checkable-objects.tf" ) ,
2024-07-23 11:45:07 -04:00
Start : hcl . Pos { Line : 41 , Column : 21 , Byte : 716 } ,
End : hcl . Pos { Line : 41 , Column : 57 , Byte : 752 } ,
2024-03-01 20:16:14 -05:00
} ,
} )
go Plan ( ctx , & req , & resp )
gotChanges , gotDiags := collectPlanOutput ( changesCh , diagsCh )
if diff := cmp . Diff ( wantDiags . ForRPC ( ) , gotDiags . ForRPC ( ) ) ; diff != "" {
t . Errorf ( "wrong diagnostics\n%s" , diff )
}
// The order of emission for our planned changes is unspecified since it
// depends on how the various goroutines get scheduled, and so we'll
// arbitrarily sort gotChanges lexically by the name of the change type
// so that we have some dependable order to diff against below.
sort . Slice ( gotChanges , func ( i , j int ) bool {
ic := gotChanges [ i ]
jc := gotChanges [ j ]
return fmt . Sprintf ( "%T" , ic ) < fmt . Sprintf ( "%T" , jc )
} )
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "single" } ,
} ,
) ,
Action : plans . Create ,
PlanApplyable : true ,
PlanComplete : true ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"foo" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "bar" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks { "foo" : nil } ,
2024-07-23 11:45:07 -04:00
PlannedOutputValues : map [ string ] cty . Value {
"foo" : cty . StringVal ( "bar" ) ,
} ,
2024-03-01 20:16:14 -05:00
PlannedCheckResults : & states . CheckResults {
ConfigResults : addrs . MakeMap (
addrs . MakeMapElem [ addrs . ConfigCheckable ] (
addrs . Check {
Name : "value_is_baz" ,
} . InModule ( addrs . RootModule ) ,
& states . CheckResultAggregate {
Status : checks . StatusFail ,
ObjectResults : addrs . MakeMap (
addrs . MakeMapElem [ addrs . Checkable ] (
addrs . Check {
Name : "value_is_baz" ,
} . Absolute ( addrs . RootModuleInstance ) ,
& states . CheckResultObject {
Status : checks . StatusFail ,
FailureMessages : [ ] string { "value must be 'baz'" } ,
} ,
) ,
) ,
} ,
) ,
addrs . MakeMapElem [ addrs . ConfigCheckable ] (
addrs . InputVariable {
Name : "foo" ,
} . InModule ( addrs . RootModule ) ,
& states . CheckResultAggregate {
Status : checks . StatusPass ,
ObjectResults : addrs . MakeMap (
addrs . MakeMapElem [ addrs . Checkable ] (
addrs . InputVariable {
Name : "foo" ,
} . Absolute ( addrs . RootModuleInstance ) ,
& states . CheckResultObject {
Status : checks . StatusPass ,
} ,
) ,
) ,
} ,
) ,
2024-07-23 11:45:07 -04:00
addrs . MakeMapElem [ addrs . ConfigCheckable ] (
addrs . OutputValue {
Name : "foo" ,
} . InModule ( addrs . RootModule ) ,
& states . CheckResultAggregate {
Status : checks . StatusPass ,
ObjectResults : addrs . MakeMap (
addrs . MakeMapElem [ addrs . Checkable ] (
addrs . OutputValue {
Name : "foo" ,
} . Absolute ( addrs . RootModuleInstance ) ,
& states . CheckResultObject {
Status : checks . StatusPass ,
} ,
) ,
) ,
} ,
) ,
2024-03-01 20:16:14 -05:00
addrs . MakeMapElem [ addrs . ConfigCheckable ] (
addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "main" ,
} . InModule ( addrs . RootModule ) ,
& states . CheckResultAggregate {
Status : checks . StatusPass ,
ObjectResults : addrs . MakeMap (
addrs . MakeMapElem [ addrs . Checkable ] (
addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "main" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
& states . CheckResultObject {
Status : checks . StatusPass ,
} ,
) ,
) ,
} ,
) ,
) ,
} ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2024-03-01 20:16:14 -05:00
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : stackaddrs . AbsResourceInstanceObject {
Component : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "single" } ,
} ,
) ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "main" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . NewDefaultProvider ( "testing" ) ,
} ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "main" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
PrevRunAddr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "main" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . NewDefaultProvider ( "testing" ) ,
} ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValueSchema ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "test" ) ,
"value" : cty . StringVal ( "bar" ) ,
2025-03-11 15:58:44 -04:00
} ) , stacks_testing_provider . TestingResourceSchema . Body ) ,
2024-03-01 20:16:14 -05:00
} ,
} ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
}
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , gotChanges , changesCmpOpts ) ; diff != "" {
2024-03-01 20:16:14 -05:00
t . Errorf ( "wrong changes\n%s" , diff )
}
}
2024-06-04 09:14:00 -04:00
func TestPlanWithDeferredResource ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "deferrable-component" )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1994-09-05T08:50:00Z" )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange )
diagsCh := make ( chan tfdiags . Diagnostic )
2024-06-27 10:08:08 -04:00
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-06-04 09:14:00 -04:00
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-06-04 09:14:00 -04:00
} ,
} ,
2024-06-27 10:08:08 -04:00
DependencyLocks : * lock ,
2024-06-04 09:14:00 -04:00
ForcePlanTimestamp : & fakePlanTimestamp ,
InputValues : map [ stackaddrs . InputVariable ] ExternalInputValue {
{ Name : "id" } : {
Value : cty . StringVal ( "62594ae3" ) ,
} ,
{ Name : "defer" } : {
Value : cty . BoolVal ( true ) ,
} ,
} ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
reportDiagnosticsForTest ( t , diags )
if len ( diags ) != 0 {
t . FailNow ( ) // We reported the diags above
}
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
} )
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
2024-07-25 02:04:24 -04:00
Applyable : true ,
2024-06-04 09:14:00 -04:00
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
PlanComplete : false ,
PlanApplyable : false , // We don't have any resources to apply since they're deferred.
Action : plans . Create ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "62594ae3" ) ) ,
"defer" : mustPlanDynamicValueDynamicType ( cty . BoolVal ( true ) ) ,
} ,
PlannedOutputValues : map [ string ] cty . Value { } ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"defer" : nil ,
} ,
} ,
& stackplan . PlannedChangeDeferredResourceInstancePlanned {
ResourceInstancePlanned : stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : stackaddrs . AbsResourceInstanceObject {
Component : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_deferred_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_deferred_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
PrevRunAddr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_deferred_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValueSchema ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "62594ae3" ) ,
"value" : cty . NullVal ( cty . String ) ,
"deferred" : cty . BoolVal ( true ) ,
2025-03-11 15:58:44 -04:00
} ) , stacks_testing_provider . DeferredResourceSchema . Body ) ,
2024-06-04 09:14:00 -04:00
AfterSensitivePaths : nil ,
} ,
} ,
Schema : stacks_testing_provider . DeferredResourceSchema ,
} ,
DeferredReason : providers . DeferredReasonResourceConfigUnknown ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2024-06-04 09:14:00 -04:00
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "defer" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . BoolVal ( true ) ,
2024-06-04 09:14:00 -04:00
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "id" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . StringVal ( "62594ae3" ) ,
2024-06-04 09:14:00 -04:00
} ,
}
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , gotChanges , changesCmpOpts ) ; diff != "" {
2024-06-04 09:14:00 -04:00
t . Errorf ( "wrong changes\n%s" , diff )
}
}
2024-04-17 06:49:42 -04:00
func TestPlanWithDeferredComponentForEach ( t * testing . T ) {
ctx := context . Background ( )
2024-06-04 09:32:50 -04:00
cfg := loadMainBundleConfigForTest ( t , path . Join ( "with-single-input-and-output" , "deferred-component-for-each" ) )
2024-04-17 06:49:42 -04:00
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
2024-06-27 10:08:08 -04:00
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-04-17 06:49:42 -04:00
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-04-17 06:49:42 -04:00
} ,
} ,
2024-06-27 10:08:08 -04:00
DependencyLocks : * lock ,
2024-04-17 06:49:42 -04:00
ForcePlanTimestamp : & fakePlanTimestamp ,
InputValues : map [ stackaddrs . InputVariable ] ExternalInputValue {
{ Name : "components" } : {
Value : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
DefRange : tfdiags . SourceRange { } ,
} ,
} ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
reportDiagnosticsForTest ( t , diags )
if len ( diags ) != 0 {
t . FailNow ( ) // We reported the diags above/
}
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
} )
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
2024-06-04 09:32:50 -04:00
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "child" } ,
} ,
) ,
PlanApplyable : true ,
PlanComplete : false ,
Action : plans . Create ,
RequiredComponents : collections . NewSet [ stackaddrs . AbsComponent ] (
stackaddrs . AbsComponent {
Stack : stackaddrs . RootStackInstance ,
Item : stackaddrs . Component {
Name : "self" ,
} ,
} ,
) ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . NullVal ( cty . String ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . UnknownVal ( cty . String ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"input" : nil ,
} ,
PlannedOutputValues : map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
} ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeDeferredResourceInstancePlanned {
ResourceInstancePlanned : stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : stackaddrs . AbsResourceInstanceObject {
Component : stackaddrs . AbsComponentInstance {
Stack : stackaddrs . RootStackInstance ,
Item : stackaddrs . ComponentInstance {
Component : stackaddrs . Component {
Name : "child" ,
} ,
} ,
} ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : addrs . AbsResourceInstance {
Module : addrs . RootModuleInstance ,
Resource : addrs . ResourceInstance {
Resource : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} ,
Key : addrs . NoKey ,
} ,
} ,
} ,
} ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
PrevRunAddr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValueSchema ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
"value" : cty . UnknownVal ( cty . String ) ,
2025-03-11 15:58:44 -04:00
} ) , stacks_testing_provider . TestingResourceSchema . Body ) ,
2024-06-04 09:32:50 -04:00
AfterSensitivePaths : nil ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
DeferredReason : providers . DeferredReasonDeferredPrereq ,
} ,
2024-04-17 06:49:42 -04:00
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
Key : addrs . WildcardKey ,
} ,
) ,
2024-06-04 09:32:50 -04:00
PlanApplyable : true , // TODO: Questionable? We only have outputs.
2024-06-04 09:24:04 -04:00
PlanComplete : false ,
2024-04-17 06:49:42 -04:00
Action : plans . Create ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . NullVal ( cty . String ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . UnknownVal ( cty . String ) ) ,
} ,
2024-06-04 09:32:50 -04:00
PlannedOutputValues : map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
} ,
2024-04-17 06:49:42 -04:00
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"input" : nil ,
} ,
} ,
2024-06-04 09:24:04 -04:00
& stackplan . PlannedChangeDeferredResourceInstancePlanned {
DeferredReason : providers . DeferredReasonDeferredPrereq ,
ResourceInstancePlanned : stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : stackaddrs . AbsResourceInstanceObject {
Component : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
Key : addrs . WildcardKey ,
} ,
) ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
2024-04-17 06:49:42 -04:00
} ,
} ,
2024-06-04 09:24:04 -04:00
ProviderConfigAddr : addrs . AbsProviderConfig {
2024-04-17 06:49:42 -04:00
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
2024-06-04 09:24:04 -04:00
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
PrevRunAddr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValueSchema ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
"value" : cty . UnknownVal ( cty . String ) ,
2025-03-11 15:58:44 -04:00
} ) , stacks_testing_provider . TestingResourceSchema . Body ) ,
2024-06-04 09:24:04 -04:00
AfterSensitivePaths : nil ,
} ,
2024-04-17 06:49:42 -04:00
} ,
2024-06-04 09:24:04 -04:00
Schema : stacks_testing_provider . TestingResourceSchema ,
2024-04-17 06:49:42 -04:00
} ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2024-04-17 06:49:42 -04:00
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "components" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
2024-04-17 06:49:42 -04:00
} ,
}
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , gotChanges , changesCmpOpts ) ; diff != "" {
2024-04-17 06:49:42 -04:00
t . Errorf ( "wrong changes\n%s" , diff )
}
}
2024-06-04 09:32:50 -04:00
func TestPlanWithDeferredComponentReferences ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , path . Join ( "with-single-input-and-output" , "deferred-component-references" ) )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
2024-06-27 10:08:08 -04:00
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-06-04 09:32:50 -04:00
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-06-04 09:32:50 -04:00
} ,
} ,
2024-06-27 10:08:08 -04:00
DependencyLocks : * lock ,
2024-06-04 09:32:50 -04:00
ForcePlanTimestamp : & fakePlanTimestamp ,
InputValues : map [ stackaddrs . InputVariable ] ExternalInputValue {
{ Name : "known_components" } : {
Value : cty . ListVal ( [ ] cty . Value { cty . StringVal ( "known" ) } ) ,
DefRange : tfdiags . SourceRange { } ,
} ,
{ Name : "unknown_components" } : {
Value : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
DefRange : tfdiags . SourceRange { } ,
} ,
} ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
reportDiagnosticsForTest ( t , diags )
if len ( diags ) != 0 {
t . FailNow ( ) // We reported the diags above.
}
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
} )
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "children" } ,
Key : addrs . WildcardKey ,
} ,
) ,
PlanApplyable : true , // TODO: Questionable? We only have outputs.
PlanComplete : false ,
Action : plans . Create ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . NullVal ( cty . String ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . UnknownVal ( cty . String ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"input" : nil ,
} ,
PlannedOutputValues : map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
} ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
RequiredComponents : collections . NewSet [ stackaddrs . AbsComponent ] (
stackaddrs . AbsComponent {
Stack : stackaddrs . RootStackInstance ,
Item : stackaddrs . Component {
Name : "self" ,
} ,
} ,
) ,
} ,
& stackplan . PlannedChangeDeferredResourceInstancePlanned {
DeferredReason : providers . DeferredReasonDeferredPrereq ,
ResourceInstancePlanned : stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : stackaddrs . AbsResourceInstanceObject {
Component : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "children" } ,
Key : addrs . WildcardKey ,
} ,
) ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
PrevRunAddr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValueSchema ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
"value" : cty . UnknownVal ( cty . String ) ,
2025-03-11 15:58:44 -04:00
} ) , stacks_testing_provider . TestingResourceSchema . Body ) ,
2024-06-04 09:32:50 -04:00
AfterSensitivePaths : nil ,
} ,
} ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
Key : addrs . StringKey ( "known" ) ,
} ) ,
PlanApplyable : true ,
PlanComplete : true ,
Action : plans . Create ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . NullVal ( cty . String ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "known" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"input" : nil ,
} ,
PlannedOutputValues : map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
} ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : stackaddrs . AbsResourceInstanceObject {
Component : stackaddrs . AbsComponentInstance {
Stack : stackaddrs . RootStackInstance ,
Item : stackaddrs . ComponentInstance {
Component : stackaddrs . Component {
Name : "self" ,
} ,
Key : addrs . StringKey ( "known" ) ,
} ,
} ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : addrs . AbsResourceInstance {
Module : addrs . RootModuleInstance ,
Resource : addrs . ResourceInstance {
Resource : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} ,
Key : addrs . NoKey ,
} ,
} ,
} ,
} ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : addrs . AbsResourceInstance {
Module : addrs . RootModuleInstance ,
Resource : addrs . ResourceInstance {
Resource : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} ,
Key : addrs . NoKey ,
} ,
} ,
PrevRunAddr : addrs . AbsResourceInstance {
Module : addrs . RootModuleInstance ,
Resource : addrs . ResourceInstance {
Resource : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} ,
Key : addrs . NoKey ,
} ,
} ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValueSchema ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
"value" : cty . StringVal ( "known" ) ,
2025-03-11 15:58:44 -04:00
} ) , stacks_testing_provider . TestingResourceSchema . Body ) ,
2024-06-04 09:32:50 -04:00
} ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2024-06-04 09:32:50 -04:00
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "known_components" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . SetVal ( [ ] cty . Value { cty . StringVal ( "known" ) } ) ,
2024-06-04 09:32:50 -04:00
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "unknown_components" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
2024-06-04 09:32:50 -04:00
} ,
}
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , gotChanges , changesCmpOpts ) ; diff != "" {
2024-05-06 09:37:14 -04:00
t . Errorf ( "wrong changes\n%s" , diff )
}
}
2024-04-19 06:50:08 -04:00
func TestPlanWithDeferredComponentForEachOfInvalidType ( t * testing . T ) {
2024-04-17 06:49:42 -04:00
ctx := context . Background ( )
2024-04-19 06:50:08 -04:00
cfg := loadMainBundleConfigForTest ( t , "deferred-component-for-each-from-component-of-invalid-type" )
2024-04-17 06:49:42 -04:00
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
2024-06-27 10:08:08 -04:00
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-04-17 06:49:42 -04:00
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-04-17 06:49:42 -04:00
} ,
} ,
2024-06-27 10:08:08 -04:00
DependencyLocks : * lock ,
2024-04-17 06:49:42 -04:00
ForcePlanTimestamp : & fakePlanTimestamp ,
InputValues : map [ stackaddrs . InputVariable ] ExternalInputValue {
{ Name : "components" } : {
Value : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
DefRange : tfdiags . SourceRange { } ,
} ,
} ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
2024-04-19 06:50:08 -04:00
_ , diags := collectPlanOutput ( changesCh , diagsCh )
2024-04-17 06:49:42 -04:00
2024-04-19 06:50:08 -04:00
if len ( diags ) != 1 {
t . Fatalf ( "expected 1 diagnostic, got %d: %s" , len ( diags ) , diags )
2024-04-17 06:49:42 -04:00
}
2024-04-19 06:50:08 -04:00
if diags [ 0 ] . Severity ( ) != tfdiags . Error {
t . Errorf ( "expected error diagnostic, got %q" , diags [ 0 ] . Severity ( ) )
2024-04-17 06:49:42 -04:00
}
2024-04-19 06:50:08 -04:00
expectedSummary := "Invalid for_each value"
if diags [ 0 ] . Description ( ) . Summary != expectedSummary {
t . Errorf ( "expected diagnostic with summary %q, got %q" , expectedSummary , diags [ 0 ] . Description ( ) . Summary )
}
2024-04-17 06:49:42 -04:00
2024-04-19 06:50:08 -04:00
expectedDetail := "The for_each expression must produce either a map of any type or a set of strings. The keys of the map or the set elements will serve as unique identifiers for multiple instances of this component."
if diags [ 0 ] . Description ( ) . Detail != expectedDetail {
t . Errorf ( "expected diagnostic with detail %q, got %q" , expectedDetail , diags [ 0 ] . Description ( ) . Detail )
2024-04-17 06:49:42 -04:00
}
}
2024-06-04 10:26:23 -04:00
func TestPlanWithDeferredProviderForEach ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , path . Join ( "with-single-input" , "deferred-provider-for-each" ) )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange )
diagsCh := make ( chan tfdiags . Diagnostic )
2024-06-27 10:08:08 -04:00
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-06-04 10:26:23 -04:00
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-06-04 10:26:23 -04:00
} ,
} ,
2024-06-27 10:08:08 -04:00
DependencyLocks : * lock ,
2024-06-04 10:26:23 -04:00
ForcePlanTimestamp : & fakePlanTimestamp ,
InputValues : map [ stackaddrs . InputVariable ] ExternalInputValue {
{ Name : "providers" } : {
Value : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
DefRange : tfdiags . SourceRange { } ,
} ,
} ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
reportDiagnosticsForTest ( t , diags )
if len ( diags ) != 0 {
t . FailNow ( ) // We reported the diags above
}
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
} )
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
2024-07-25 02:04:24 -04:00
Applyable : true ,
2024-06-04 10:26:23 -04:00
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "known" } ,
} ) ,
PlanComplete : false ,
PlanApplyable : false ,
Action : plans . Create ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . NullVal ( cty . String ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "primary" ) ) ,
} ,
PlannedOutputValues : map [ string ] cty . Value { } ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"input" : nil ,
} ,
} ,
& stackplan . PlannedChangeDeferredResourceInstancePlanned {
ResourceInstancePlanned : stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : stackaddrs . AbsResourceInstanceObject {
Component : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "known" } ,
} ,
) ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
PrevRunAddr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValueSchema ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
"value" : cty . StringVal ( "primary" ) ,
2025-03-11 15:58:44 -04:00
} ) , stacks_testing_provider . TestingResourceSchema . Body ) ,
2024-06-04 10:26:23 -04:00
} ,
} ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
DeferredReason : providers . DeferredReasonProviderConfigUnknown ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "unknown" } ,
Key : addrs . WildcardKey ,
} ) ,
PlanComplete : false ,
PlanApplyable : false ,
Action : plans . Create ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . NullVal ( cty . String ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "secondary" ) ) ,
} ,
PlannedOutputValues : map [ string ] cty . Value { } ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"input" : nil ,
} ,
} ,
& stackplan . PlannedChangeDeferredResourceInstancePlanned {
ResourceInstancePlanned : stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : stackaddrs . AbsResourceInstanceObject {
Component : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "unknown" } ,
Key : addrs . WildcardKey ,
} ,
) ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
} ,
} ,
ProviderConfigAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
PrevRunAddr : addrs . Resource {
Mode : addrs . ManagedResourceMode ,
Type : "testing_resource" ,
Name : "data" ,
} . Instance ( addrs . NoKey ) . Absolute ( addrs . RootModuleInstance ) ,
ProviderAddr : addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . MustParseProviderSourceString ( "hashicorp/testing" ) ,
} ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValueSchema ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
"value" : cty . StringVal ( "secondary" ) ,
2025-03-11 15:58:44 -04:00
} ) , stacks_testing_provider . TestingResourceSchema . Body ) ,
2024-06-04 10:26:23 -04:00
} ,
} ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
DeferredReason : providers . DeferredReasonProviderConfigUnknown ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2024-06-04 10:26:23 -04:00
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "providers" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
2024-06-04 10:26:23 -04:00
} ,
}
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , gotChanges , changesCmpOpts ) ; diff != "" {
2024-06-06 07:36:57 -04:00
t . Errorf ( "wrong changes\n%s" , diff )
}
}
func TestPlanInvalidProvidersFailGracefully ( t * testing . T ) {
ctx := context . Background ( )
2024-08-22 03:28:39 -04:00
cfg := loadMainBundleConfigForTest ( t , path . Join ( "invalid-providers" ) )
2024-06-06 07:36:57 -04:00
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange )
diagsCh := make ( chan tfdiags . Diagnostic )
2024-06-27 10:08:08 -04:00
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-06-06 07:36:57 -04:00
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-06-06 07:36:57 -04:00
} ,
} ,
2024-06-27 10:08:08 -04:00
DependencyLocks : * lock ,
2024-06-06 07:36:57 -04:00
ForcePlanTimestamp : & fakePlanTimestamp ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
changes , diags := collectPlanOutput ( changesCh , diagsCh )
2024-08-12 08:54:32 -04:00
sort . SliceStable ( diags , diagnosticSortFunc ( diags ) )
2024-06-06 07:36:57 -04:00
expectDiagnosticsForTest ( t , diags ,
2024-08-12 08:54:32 -04:00
expectDiagnostic ( tfdiags . Error , "Provider configuration is invalid" , "Cannot plan changes for this resource because its associated provider configuration is invalid." ) ,
expectDiagnostic ( tfdiags . Error , "invalid configuration" , "configure_error attribute was set" ) )
2024-06-06 07:36:57 -04:00
sort . SliceStable ( changes , func ( i , j int ) bool {
return plannedChangeSortKey ( changes [ i ] ) < plannedChangeSortKey ( changes [ j ] )
} )
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable { } ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
Action : plans . Create ,
PlanTimestamp : fakePlanTimestamp ,
PlannedInputValues : make ( map [ string ] plans . DynamicValue ) ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2024-06-06 07:36:57 -04:00
}
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , changes , changesCmpOpts ) ; diff != "" {
2024-06-04 10:26:23 -04:00
t . Errorf ( "wrong changes\n%s" , diff )
}
}
2024-06-20 05:04:22 -04:00
func TestPlanWithStateManipulation ( t * testing . T ) {
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
2024-06-27 10:08:08 -04:00
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-06-20 05:04:22 -04:00
tcs := map [ string ] struct {
state * stackstate . State
store * stacks_testing_provider . ResourceStore
inputs map [ string ] cty . Value
changes [ ] stackplan . PlannedChange
2024-07-01 08:23:17 -04:00
counts collections . Map [ stackaddrs . AbsComponentInstance , * hooks . ComponentInstanceChange ]
2024-06-20 05:04:22 -04:00
expectedWarnings [ ] string
} {
"moved" : {
state : stackstate . NewStateBuilder ( ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "component.self.testing_resource.before" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "moved" ,
"value" : "moved" ,
} ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "moved" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "moved" ) ,
"value" : cty . StringVal ( "moved" ) ,
} ) ) .
Build ( ) ,
changes : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.self" ) ,
PlanApplyable : true ,
PlanComplete : true ,
Action : plans . Update ,
PlannedInputValues : make ( map [ string ] plans . DynamicValue ) ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
RequiredComponents : collections . NewSet [ stackaddrs . AbsComponent ] ( ) ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.self.testing_resource.after" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.after" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.before" ) ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . NoOp ,
Before : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "moved" ) ,
"value" : cty . StringVal ( "moved" ) ,
} ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "moved" ) ,
"value" : cty . StringVal ( "moved" ) ,
} ) ) ,
} ,
} ,
PriorStateSrc : & states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "moved" ,
"value" : "moved" ,
} ) ,
Dependencies : make ( [ ] addrs . ConfigResource , 0 ) ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2024-08-22 03:20:47 -04:00
} ,
counts : collections . NewMap [ stackaddrs . AbsComponentInstance , * hooks . ComponentInstanceChange ] (
collections . MapElem [ stackaddrs . AbsComponentInstance , * hooks . ComponentInstanceChange ] {
K : mustAbsComponentInstance ( "component.self" ) ,
V : & hooks . ComponentInstanceChange {
Addr : mustAbsComponentInstance ( "component.self" ) ,
Move : 1 ,
} ,
} ) ,
} ,
"cross-type-moved" : {
state : stackstate . NewStateBuilder ( ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "component.self.testing_resource.before" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "moved" ,
"value" : "moved" ,
} ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "moved" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "moved" ) ,
"value" : cty . StringVal ( "moved" ) ,
} ) ) .
Build ( ) ,
changes : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.self" ) ,
PlanApplyable : true ,
PlanComplete : true ,
Action : plans . Update ,
PlannedInputValues : make ( map [ string ] plans . DynamicValue ) ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
RequiredComponents : collections . NewSet [ stackaddrs . AbsComponent ] ( ) ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.self.testing_deferred_resource.after" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_deferred_resource.after" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.before" ) ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . NoOp ,
Before : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "moved" ) ,
"value" : cty . StringVal ( "moved" ) ,
"deferred" : cty . False ,
} ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "moved" ) ,
"value" : cty . StringVal ( "moved" ) ,
"deferred" : cty . False ,
} ) ) ,
} ,
} ,
PriorStateSrc : & states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "moved" ,
"value" : "moved" ,
"deferred" : false ,
} ) ,
Dependencies : make ( [ ] addrs . ConfigResource , 0 ) ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . DeferredResourceSchema ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2024-06-20 05:04:22 -04:00
} ,
2024-07-01 08:23:17 -04:00
counts : collections . NewMap [ stackaddrs . AbsComponentInstance , * hooks . ComponentInstanceChange ] (
collections . MapElem [ stackaddrs . AbsComponentInstance , * hooks . ComponentInstanceChange ] {
K : mustAbsComponentInstance ( "component.self" ) ,
V : & hooks . ComponentInstanceChange {
Addr : mustAbsComponentInstance ( "component.self" ) ,
Move : 1 ,
} ,
} ) ,
2024-06-20 05:04:22 -04:00
} ,
"import" : {
state : stackstate . NewStateBuilder ( ) . Build ( ) , // We start with an empty state for this.
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "imported" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "imported" ) ,
"value" : cty . StringVal ( "imported" ) ,
} ) ) .
Build ( ) ,
inputs : map [ string ] cty . Value {
"id" : cty . StringVal ( "imported" ) ,
} ,
changes : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.self" ) ,
PlanApplyable : true ,
PlanComplete : true ,
// The component is still CREATE even though all the
// instances are NoOps, because the component itself didn't
// exist before even though all the resources might have.
Action : plans . Create ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "imported" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
RequiredComponents : collections . NewSet [ stackaddrs . AbsComponent ] ( ) ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.self.testing_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . NoOp ,
Before : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "imported" ) ,
"value" : cty . StringVal ( "imported" ) ,
} ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "imported" ) ,
"value" : cty . StringVal ( "imported" ) ,
} ) ) ,
Importing : & plans . ImportingSrc {
ID : "imported" ,
} ,
} ,
} ,
PriorStateSrc : & states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "imported" ,
"value" : "imported" ,
} ) ,
Dependencies : make ( [ ] addrs . ConfigResource , 0 ) ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2024-06-20 05:04:22 -04:00
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable {
Name : "id" ,
} ,
2024-09-16 05:45:19 -04:00
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . StringVal ( "imported" ) ,
2024-06-20 05:04:22 -04:00
RequiredOnApply : false ,
} ,
} ,
2024-07-01 08:23:17 -04:00
counts : collections . NewMap [ stackaddrs . AbsComponentInstance , * hooks . ComponentInstanceChange ] (
collections . MapElem [ stackaddrs . AbsComponentInstance , * hooks . ComponentInstanceChange ] {
K : mustAbsComponentInstance ( "component.self" ) ,
V : & hooks . ComponentInstanceChange {
Addr : mustAbsComponentInstance ( "component.self" ) ,
Import : 1 ,
} ,
} ) ,
2024-06-20 05:04:22 -04:00
} ,
"removed" : {
state : stackstate . NewStateBuilder ( ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "component.self.testing_resource.resource" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "removed" ,
"value" : "removed" ,
} ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "removed" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "removed" ) ,
"value" : cty . StringVal ( "removed" ) ,
} ) ) .
Build ( ) ,
changes : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.self" ) ,
PlanApplyable : true ,
PlanComplete : true ,
Action : plans . Update ,
PlannedInputValues : make ( map [ string ] plans . DynamicValue ) ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
RequiredComponents : collections . NewSet [ stackaddrs . AbsComponent ] ( ) ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.self.testing_resource.resource" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.resource" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.resource" ) ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Forget ,
Before : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "removed" ) ,
"value" : cty . StringVal ( "removed" ) ,
} ) ) ,
After : mustPlanDynamicValue ( cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"id" : cty . String ,
"value" : cty . String ,
} ) ) ) ,
} ,
ActionReason : plans . ResourceInstanceDeleteBecauseNoResourceConfig ,
} ,
PriorStateSrc : & states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "removed" ,
"value" : "removed" ,
} ) ,
Dependencies : make ( [ ] addrs . ConfigResource , 0 ) ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
2024-06-21 08:05:26 -04:00
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
2024-06-20 05:04:22 -04:00
} ,
2024-07-01 08:23:17 -04:00
counts : collections . NewMap [ stackaddrs . AbsComponentInstance , * hooks . ComponentInstanceChange ] (
collections . MapElem [ stackaddrs . AbsComponentInstance , * hooks . ComponentInstanceChange ] {
K : mustAbsComponentInstance ( "component.self" ) ,
V : & hooks . ComponentInstanceChange {
Addr : mustAbsComponentInstance ( "component.self" ) ,
Forget : 1 ,
} ,
} ) ,
2024-06-20 05:04:22 -04:00
expectedWarnings : [ ] string { "Some objects will no longer be managed by Terraform" } ,
} ,
}
for name , tc := range tcs {
t . Run ( name , func ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , path . Join ( "state-manipulation" , name ) )
2024-07-01 08:23:17 -04:00
gotCounts := collections . NewMap [ stackaddrs . AbsComponentInstance , * hooks . ComponentInstanceChange ] ( )
ctx = ContextWithHooks ( ctx , & stackeval . Hooks {
ReportComponentInstancePlanned : func ( ctx context . Context , span any , change * hooks . ComponentInstanceChange ) any {
gotCounts . Put ( change . Addr , change )
return span
} ,
} )
2024-06-20 05:04:22 -04:00
inputs := make ( map [ stackaddrs . InputVariable ] ExternalInputValue , len ( tc . inputs ) )
for name , input := range tc . inputs {
inputs [ stackaddrs . InputVariable { Name : name } ] = ExternalInputValue {
Value : input ,
}
}
changesCh := make ( chan stackplan . PlannedChange )
diagsCh := make ( chan tfdiags . Diagnostic )
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProviderWithData ( t , tc . store ) , nil
2024-06-20 05:04:22 -04:00
} ,
} ,
2024-06-27 10:08:08 -04:00
DependencyLocks : * lock ,
2024-06-20 05:04:22 -04:00
InputValues : inputs ,
ForcePlanTimestamp : & fakePlanTimestamp ,
PrevState : tc . state ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
changes , diags := collectPlanOutput ( changesCh , diagsCh )
reportDiagnosticsForTest ( t , diags )
if len ( diags ) > len ( tc . expectedWarnings ) {
t . Fatalf ( "had unexpected warnings" )
}
for i , diag := range diags {
if diag . Description ( ) . Summary != tc . expectedWarnings [ i ] {
t . Fatalf ( "expected diagnostic with summary %q, got %q" , tc . expectedWarnings [ i ] , diag . Description ( ) . Summary )
}
}
sort . SliceStable ( changes , func ( i , j int ) bool {
return plannedChangeSortKey ( changes [ i ] ) < plannedChangeSortKey ( changes [ j ] )
} )
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( tc . changes , changes , changesCmpOpts ) ; diff != "" {
2024-06-20 05:04:22 -04:00
t . Errorf ( "wrong changes\n%s" , diff )
}
2024-07-01 08:23:17 -04:00
wantCounts := tc . counts
2024-10-03 15:02:12 -04:00
for key , elem := range wantCounts . All ( ) {
2024-07-01 08:23:17 -04:00
// First, make sure everything we wanted is present.
2024-10-03 15:02:12 -04:00
if ! gotCounts . HasKey ( key ) {
t . Errorf ( "wrong counts: wanted %s but didn't get it" , key )
2024-07-01 08:23:17 -04:00
}
// And that the values actually match.
2024-10-03 15:02:12 -04:00
got , want := gotCounts . Get ( key ) , elem
2024-07-01 08:23:17 -04:00
if diff := cmp . Diff ( want , got ) ; diff != "" {
t . Errorf ( "wrong counts for %s: %s" , want . Addr , diff )
}
}
2024-10-03 15:02:12 -04:00
for key := range gotCounts . All ( ) {
2024-07-01 08:23:17 -04:00
// Then, make sure we didn't get anything we didn't want.
2024-10-03 15:02:12 -04:00
if ! wantCounts . HasKey ( key ) {
t . Errorf ( "wrong counts: got %s but didn't want it" , key )
2024-07-01 08:23:17 -04:00
}
}
2024-06-20 05:04:22 -04:00
} )
}
}
2024-06-20 07:10:23 -04:00
func TestPlan_plantimestamp_force_timestamp ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "with-plantimestamp" )
forcedPlanTimestamp := "1991-08-25T20:57:08Z"
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , forcedPlanTimestamp )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
// We support both hashicorp/testing and
// terraform.io/builtin/testing as providers. This lets us
// test the provider aliasing feature. Both providers
// support the same set of resources and data sources.
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-06-20 07:10:23 -04:00
} ,
addrs . NewBuiltInProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-06-20 07:10:23 -04:00
} ,
} ,
InputValues : func ( ) map [ stackaddrs . InputVariable ] ExternalInputValue {
return map [ stackaddrs . InputVariable ] ExternalInputValue { }
} ( ) ,
ForcePlanTimestamp : & fakePlanTimestamp ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
2024-06-21 08:05:26 -04:00
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
2024-06-20 07:10:23 -04:00
// The following will fail the test if there are any error
// diagnostics.
reportDiagnosticsForTest ( t , diags )
// We also want to fail if there are just warnings, since the
// configurations here are supposed to be totally problem-free.
if len ( diags ) != 0 {
// reportDiagnosticsForTest already showed the diagnostics in
// the log
t . FailNow ( )
}
2024-06-21 08:05:26 -04:00
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
} )
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "second-self" } ,
} ,
) ,
Action : plans . Create ,
PlanApplyable : true ,
PlanComplete : true ,
PlannedCheckResults : & states . CheckResults { } ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"value" : nil ,
} ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"value" : mustPlanDynamicValueDynamicType ( cty . StringVal ( forcedPlanTimestamp ) ) ,
} ,
PlannedOutputValues : map [ string ] cty . Value {
"input" : cty . StringVal ( forcedPlanTimestamp ) ,
"out" : cty . StringVal ( fmt . Sprintf ( "module-output-%s" , forcedPlanTimestamp ) ) ,
} ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
Action : plans . Create ,
PlanApplyable : true ,
PlanComplete : true ,
PlannedCheckResults : & states . CheckResults { } ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"value" : nil ,
} ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"value" : mustPlanDynamicValueDynamicType ( cty . StringVal ( forcedPlanTimestamp ) ) ,
} ,
PlannedOutputValues : map [ string ] cty . Value {
"input" : cty . StringVal ( forcedPlanTimestamp ) ,
"out" : cty . StringVal ( fmt . Sprintf ( "module-output-%s" , forcedPlanTimestamp ) ) ,
} ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangeOutputValue {
2024-09-16 05:36:36 -04:00
Addr : stackaddrs . OutputValue { Name : "plantimestamp" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . StringVal ( forcedPlanTimestamp ) ,
2024-06-21 08:05:26 -04:00
} ,
& stackplan . PlannedChangePlannedTimestamp { PlannedTimestamp : fakePlanTimestamp } ,
}
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , gotChanges , changesCmpOpts ) ; diff != "" {
2024-06-21 08:05:26 -04:00
t . Errorf ( "wrong changes\n%s" , diff )
}
2024-06-20 07:10:23 -04:00
}
func TestPlan_plantimestamp_later_than_when_writing_this_test ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "with-plantimestamp" )
dayOfWritingThisTest := "2024-06-21T06:37:08Z"
dayOfWritingThisTestTime , err := time . Parse ( time . RFC3339 , dayOfWritingThisTest )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
// We support both hashicorp/testing and
// terraform.io/builtin/testing as providers. This lets us
// test the provider aliasing feature. Both providers
// support the same set of resources and data sources.
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-06-20 07:10:23 -04:00
} ,
addrs . NewBuiltInProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-06-20 07:10:23 -04:00
} ,
} ,
InputValues : func ( ) map [ stackaddrs . InputVariable ] ExternalInputValue {
return map [ stackaddrs . InputVariable ] ExternalInputValue { }
} ( ) ,
ForcePlanTimestamp : nil , // This is what we want to test
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
changes , diags := collectPlanOutput ( changesCh , diagsCh )
output := expectOutput ( t , "plantimestamp" , changes )
2024-09-16 05:36:36 -04:00
plantimestampValue := output . After
2024-06-20 07:10:23 -04:00
plantimestamp , err := time . Parse ( time . RFC3339 , plantimestampValue . AsString ( ) )
if err != nil {
t . Fatal ( err )
}
if plantimestamp . Before ( dayOfWritingThisTestTime ) {
t . Errorf ( "expected plantimestamp to be later than %q, got %q" , dayOfWritingThisTest , plantimestampValue . AsString ( ) )
}
// The following will fail the test if there are any error
// diagnostics.
reportDiagnosticsForTest ( t , diags )
// We also want to fail if there are just warnings, since the
// configurations here are supposed to be totally problem-free.
if len ( diags ) != 0 {
// reportDiagnosticsForTest already showed the diagnostics in
// the log
t . FailNow ( )
}
}
2024-08-12 09:02:36 -04:00
func TestPlan_DependsOnUpdatesRequirements ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , path . Join ( "with-single-input" , "depends-on" ) )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
changesCh := make ( chan stackplan . PlannedChange )
diagsCh := make ( chan tfdiags . Diagnostic )
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-08-12 09:02:36 -04:00
} ,
} ,
DependencyLocks : * lock ,
ForcePlanTimestamp : & fakePlanTimestamp ,
InputValues : map [ stackaddrs . InputVariable ] ExternalInputValue {
{ Name : "input" } : {
Value : cty . StringVal ( "hello, world!" ) ,
} ,
} ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
reportDiagnosticsForTest ( t , diags )
if len ( diags ) != 0 {
t . FailNow ( )
}
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
} )
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.first" ) ,
PlanApplyable : true ,
PlanComplete : true ,
Action : plans . Create ,
RequiredComponents : collections . NewSet [ stackaddrs . AbsComponent ] ( ) ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . NullVal ( cty . String ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "hello, world!" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"input" : nil ,
} ,
PlanTimestamp : fakePlanTimestamp ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.first.testing_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"id" : cty . String ,
"value" : cty . String ,
} ) ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
"value" : cty . StringVal ( "hello, world!" ) ,
} ) ) ,
} ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.second" ) ,
PlanApplyable : true ,
PlanComplete : true ,
Action : plans . Create ,
RequiredComponents : collections . NewSet [ stackaddrs . AbsComponent ] (
mustAbsComponent ( "component.first" ) ,
mustAbsComponent ( "stack.second.component.self" ) ,
) ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . NullVal ( cty . String ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "hello, world!" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"input" : nil ,
} ,
PlanTimestamp : fakePlanTimestamp ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.second.testing_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"id" : cty . String ,
"value" : cty . String ,
} ) ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
"value" : cty . StringVal ( "hello, world!" ) ,
} ) ) ,
} ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "stack.first.component.self" ) ,
PlanApplyable : true ,
PlanComplete : true ,
Action : plans . Create ,
RequiredComponents : collections . NewSet [ stackaddrs . AbsComponent ] (
mustAbsComponent ( "component.first" ) ,
mustAbsComponent ( "component.empty" ) ,
) ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . NullVal ( cty . String ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "hello, world!" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"input" : nil ,
} ,
PlanTimestamp : fakePlanTimestamp ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "stack.first.component.self.testing_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"id" : cty . String ,
"value" : cty . String ,
} ) ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
"value" : cty . StringVal ( "hello, world!" ) ,
} ) ) ,
} ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "stack.second.component.self" ) ,
PlanApplyable : true ,
PlanComplete : true ,
Action : plans . Create ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . NullVal ( cty . String ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "hello, world!" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"id" : nil ,
"input" : nil ,
} ,
PlanTimestamp : fakePlanTimestamp ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "stack.second.component.self.testing_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"id" : cty . String ,
"value" : cty . String ,
} ) ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
"value" : cty . StringVal ( "hello, world!" ) ,
} ) ) ,
} ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable {
Name : "empty" ,
} ,
2024-09-16 05:45:19 -04:00
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . SetValEmpty ( cty . String ) ,
2024-08-12 09:02:36 -04:00
} ,
& stackplan . PlannedChangeRootInputValue {
Addr : stackaddrs . InputVariable {
Name : "input" ,
} ,
2024-09-16 05:45:19 -04:00
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . StringVal ( "hello, world!" ) ,
2024-08-12 09:02:36 -04:00
} ,
}
2024-08-21 15:30:01 -04:00
if diff := cmp . Diff ( wantChanges , gotChanges , changesCmpOpts ) ; diff != "" {
2024-08-12 09:02:36 -04:00
t . Errorf ( "wrong changes\n%s" , diff )
}
}
2024-09-07 08:54:32 -04:00
func TestPlan_RemovedBlocks ( t * testing . T ) {
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
tcs := map [ string ] struct {
source string
initialState * stackstate . State
store * stacks_testing_provider . ResourceStore
inputs map [ string ] cty . Value
wantPlanChanges [ ] stackplan . PlannedChange
wantPlanDiags [ ] expectedDiagnostic
} {
2024-09-10 02:59:04 -04:00
"unknown removed block with nothing to remove" : {
source : filepath . Join ( "with-single-input" , "removed-component-instance" ) ,
initialState : stackstate . NewStateBuilder ( ) .
// we have a single component instance in state
AddComponentInstance ( stackstate . NewComponentInstanceBuilder ( mustAbsComponentInstance ( "component.self[\"a\"]" ) ) .
AddInputVariable ( "id" , cty . StringVal ( "a" ) ) .
AddInputVariable ( "input" , cty . StringVal ( "a" ) ) ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "component.self[\"a\"].testing_resource.data" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "a" ,
"value" : "a" ,
} ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "a" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "a" ) ,
"value" : cty . StringVal ( "a" ) ,
} ) ) .
Build ( ) ,
inputs : map [ string ] cty . Value {
"input" : cty . SetVal ( [ ] cty . Value {
cty . StringVal ( "a" ) ,
} ) ,
"removed" : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
} ,
wantPlanChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.self[\"a\"]" ) ,
PlanComplete : true ,
PlanApplyable : false , // all changes are no-ops
Action : plans . Update ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "a" ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "a" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"input" : nil ,
"id" : nil ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.self[\"a\"].testing_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . NoOp ,
Before : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "a" ) ,
"value" : cty . StringVal ( "a" ) ,
} ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "a" ) ,
"value" : cty . StringVal ( "a" ) ,
} ) ) ,
} ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
} ,
PriorStateSrc : & states . ResourceInstanceObjectSrc {
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] interface { } {
"id" : "a" ,
"value" : "a" ,
} ) ,
Status : states . ObjectReady ,
Dependencies : make ( [ ] addrs . ConfigResource , 0 ) ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "input" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . SetVal ( [ ] cty . Value {
2024-09-10 02:59:04 -04:00
cty . StringVal ( "a" ) ,
} ) ,
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "removed" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
2024-09-10 02:59:04 -04:00
} ,
} ,
} ,
"unknown removed block with elements in state" : {
source : filepath . Join ( "with-single-input" , "removed-component-instance" ) ,
initialState : stackstate . NewStateBuilder ( ) .
// we have a single component instance in state
AddComponentInstance ( stackstate . NewComponentInstanceBuilder ( mustAbsComponentInstance ( "component.self[\"a\"]" ) ) .
AddInputVariable ( "id" , cty . StringVal ( "a" ) ) .
AddInputVariable ( "input" , cty . StringVal ( "a" ) ) ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "component.self[\"a\"].testing_resource.data" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "a" ,
"value" : "a" ,
} ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "a" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "a" ) ,
"value" : cty . StringVal ( "a" ) ,
} ) ) .
Build ( ) ,
inputs : map [ string ] cty . Value {
"input" : cty . SetValEmpty ( cty . String ) ,
"removed" : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
} ,
wantPlanChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.self[\"a\"]" ) ,
PlanComplete : false , // has deferred changes
PlanApplyable : false , // only deferred changes
Action : plans . Delete ,
Mode : plans . DestroyMode ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "a" ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "a" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"input" : nil ,
"id" : nil ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeDeferredResourceInstancePlanned {
ResourceInstancePlanned : stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.self[\"a\"].testing_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Delete ,
Before : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "a" ) ,
"value" : cty . StringVal ( "a" ) ,
} ) ) ,
After : mustPlanDynamicValue ( cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"id" : cty . String ,
"value" : cty . String ,
} ) ) ) ,
} ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
} ,
PriorStateSrc : & states . ResourceInstanceObjectSrc {
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] interface { } {
"id" : "a" ,
"value" : "a" ,
} ) ,
Status : states . ObjectReady ,
Dependencies : make ( [ ] addrs . ConfigResource , 0 ) ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
DeferredReason : providers . DeferredReasonDeferredPrereq ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "input" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . SetValEmpty ( cty . String ) ,
2024-09-10 02:59:04 -04:00
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "removed" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
2024-09-10 02:59:04 -04:00
} ,
} ,
} ,
"unknown component block with element to remove" : {
source : filepath . Join ( "with-single-input" , "removed-component-instance" ) ,
initialState : stackstate . NewStateBuilder ( ) .
AddComponentInstance ( stackstate . NewComponentInstanceBuilder ( mustAbsComponentInstance ( "component.self[\"a\"]" ) ) .
AddInputVariable ( "id" , cty . StringVal ( "a" ) ) .
AddInputVariable ( "input" , cty . StringVal ( "a" ) ) ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "component.self[\"a\"].testing_resource.data" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "a" ,
"value" : "a" ,
} ) ,
} ) ) .
AddComponentInstance ( stackstate . NewComponentInstanceBuilder ( mustAbsComponentInstance ( "component.self[\"b\"]" ) ) .
AddInputVariable ( "id" , cty . StringVal ( "b" ) ) .
AddInputVariable ( "input" , cty . StringVal ( "b" ) ) ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "component.self[\"b\"].testing_resource.data" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "b" ,
"value" : "b" ,
} ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "a" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "a" ) ,
"value" : cty . StringVal ( "a" ) ,
} ) ) .
AddResource ( "b" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "b" ) ,
"value" : cty . StringVal ( "b" ) ,
} ) ) .
Build ( ) ,
inputs : map [ string ] cty . Value {
"input" : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
"removed" : cty . SetVal ( [ ] cty . Value { cty . StringVal ( "b" ) } ) ,
} ,
wantPlanChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.self[\"a\"]" ) ,
PlanComplete : false , // has deferred changes
PlanApplyable : false , // only deferred changes
Action : plans . Update ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "a" ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "a" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"input" : nil ,
"id" : nil ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeDeferredResourceInstancePlanned {
ResourceInstancePlanned : stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.self[\"a\"].testing_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . NoOp ,
Before : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "a" ) ,
"value" : cty . StringVal ( "a" ) ,
} ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "a" ) ,
"value" : cty . StringVal ( "a" ) ,
} ) ) ,
} ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
} ,
PriorStateSrc : & states . ResourceInstanceObjectSrc {
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] interface { } {
"id" : "a" ,
"value" : "a" ,
} ) ,
Status : states . ObjectReady ,
Dependencies : make ( [ ] addrs . ConfigResource , 0 ) ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
DeferredReason : providers . DeferredReasonDeferredPrereq ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.self[\"b\"]" ) ,
PlanComplete : true ,
PlanApplyable : true ,
Action : plans . Delete ,
Mode : plans . DestroyMode ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "b" ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "b" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"input" : nil ,
"id" : nil ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.self[\"b\"].testing_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Delete ,
Before : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "b" ) ,
"value" : cty . StringVal ( "b" ) ,
} ) ) ,
After : mustPlanDynamicValue ( cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"id" : cty . String ,
"value" : cty . String ,
} ) ) ) ,
} ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
} ,
PriorStateSrc : & states . ResourceInstanceObjectSrc {
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] interface { } {
"id" : "b" ,
"value" : "b" ,
} ) ,
Status : states . ObjectReady ,
Dependencies : make ( [ ] addrs . ConfigResource , 0 ) ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "input" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
2024-09-10 02:59:04 -04:00
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "removed" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . SetVal ( [ ] cty . Value { cty . StringVal ( "b" ) } ) ,
2024-09-10 02:59:04 -04:00
} ,
} ,
} ,
"unknown component and removed block with element in state" : {
source : filepath . Join ( "with-single-input" , "removed-component-instance" ) ,
initialState : stackstate . NewStateBuilder ( ) .
AddComponentInstance ( stackstate . NewComponentInstanceBuilder ( mustAbsComponentInstance ( "component.self[\"a\"]" ) ) .
AddInputVariable ( "id" , cty . StringVal ( "a" ) ) .
AddInputVariable ( "input" , cty . StringVal ( "a" ) ) ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "component.self[\"a\"].testing_resource.data" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "a" ,
"value" : "a" ,
} ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "a" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "a" ) ,
"value" : cty . StringVal ( "a" ) ,
} ) ) .
Build ( ) ,
inputs : map [ string ] cty . Value {
"input" : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
"removed" : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
} ,
wantPlanChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.self[\"a\"]" ) ,
PlanComplete : false , // has deferred changes
PlanApplyable : false , // only deferred changes
Action : plans . Update ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "a" ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "a" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"input" : nil ,
"id" : nil ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeDeferredResourceInstancePlanned {
ResourceInstancePlanned : stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.self[\"a\"].testing_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . NoOp ,
Before : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "a" ) ,
"value" : cty . StringVal ( "a" ) ,
} ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "a" ) ,
"value" : cty . StringVal ( "a" ) ,
} ) ) ,
} ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
} ,
PriorStateSrc : & states . ResourceInstanceObjectSrc {
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] interface { } {
"id" : "a" ,
"value" : "a" ,
} ) ,
Status : states . ObjectReady ,
Dependencies : make ( [ ] addrs . ConfigResource , 0 ) ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
DeferredReason : providers . DeferredReasonDeferredPrereq ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "input" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
2024-09-10 02:59:04 -04:00
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "removed" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . UnknownVal ( cty . Set ( cty . String ) ) ,
2024-09-10 02:59:04 -04:00
} ,
} ,
} ,
2024-09-10 09:56:37 -04:00
"absent component" : {
source : filepath . Join ( "with-single-input" , "removed-component" ) ,
wantPlanChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
} ,
} ,
"absent component instance" : {
source : filepath . Join ( "with-single-input" , "removed-component-instance" ) ,
initialState : stackstate . NewStateBuilder ( ) .
AddComponentInstance ( stackstate . NewComponentInstanceBuilder ( mustAbsComponentInstance ( "component.self[\"removed\"]" ) ) .
AddInputVariable ( "id" , cty . StringVal ( "a" ) ) .
AddInputVariable ( "input" , cty . StringVal ( "a" ) ) ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "component.self[\"a\"].testing_resource.data" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "a" ,
"value" : "a" ,
} ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "a" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "a" ) ,
"value" : cty . StringVal ( "a" ) ,
} ) ) .
Build ( ) ,
inputs : map [ string ] cty . Value {
"input" : cty . SetVal ( [ ] cty . Value {
cty . StringVal ( "a" ) ,
} ) ,
"removed" : cty . SetVal ( [ ] cty . Value {
cty . StringVal ( "b" ) , // Doesn't exist!
} ) ,
} ,
wantPlanChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
// we're expecting the new component to be created
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.self[\"a\"]" ) ,
PlanComplete : true ,
PlanApplyable : false , // no changes
Action : plans . Update ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "a" ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "a" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"input" : nil ,
"id" : nil ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.self[\"a\"].testing_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . NoOp ,
Before : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "a" ) ,
"value" : cty . StringVal ( "a" ) ,
} ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "a" ) ,
"value" : cty . StringVal ( "a" ) ,
} ) ) ,
} ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
} ,
PriorStateSrc : & states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "a" ,
"value" : "a" ,
} ) ,
Dependencies : make ( [ ] addrs . ConfigResource , 0 ) ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
2024-09-18 04:41:36 -04:00
& stackplan . PlannedChangeComponentInstanceRemoved {
Addr : mustAbsComponentInstance ( "component.self[\"removed\"]" ) ,
} ,
2024-09-10 09:56:37 -04:00
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "input" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . SetVal ( [ ] cty . Value {
2024-09-10 09:56:37 -04:00
cty . StringVal ( "a" ) ,
} ) ,
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "removed" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . SetVal ( [ ] cty . Value {
2024-09-10 09:56:37 -04:00
cty . StringVal ( "b" ) ,
} ) ,
} ,
} ,
} ,
2024-09-07 08:54:32 -04:00
"orphaned component" : {
source : filepath . Join ( "with-single-input" , "removed-component-instance" ) ,
initialState : stackstate . NewStateBuilder ( ) .
AddComponentInstance ( stackstate . NewComponentInstanceBuilder ( mustAbsComponentInstance ( "component.self[\"removed\"]" ) ) .
AddInputVariable ( "id" , cty . StringVal ( "removed" ) ) .
AddInputVariable ( "input" , cty . StringVal ( "removed" ) ) ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "component.self[\"removed\"].testing_resource.data" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "removed" ,
"value" : "removed" ,
} ) ,
} ) ) .
AddComponentInstance ( stackstate . NewComponentInstanceBuilder ( mustAbsComponentInstance ( "component.self[\"orphaned\"]" ) ) .
AddInputVariable ( "id" , cty . StringVal ( "orphaned" ) ) .
AddInputVariable ( "input" , cty . StringVal ( "orphaned" ) ) ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "component.self[\"orphaned\"].testing_resource.data" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "orphaned" ,
"value" : "orphaned" ,
} ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "removed" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "removed" ) ,
"value" : cty . StringVal ( "removed" ) ,
} ) ) .
AddResource ( "orphaned" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "orphaned" ) ,
"value" : cty . StringVal ( "orphaned" ) ,
} ) ) .
Build ( ) ,
inputs : map [ string ] cty . Value {
"input" : cty . SetVal ( [ ] cty . Value {
cty . StringVal ( "added" ) ,
} ) ,
"removed" : cty . SetVal ( [ ] cty . Value {
cty . StringVal ( "removed" ) ,
} ) ,
} ,
wantPlanChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : false , // No! We have an unclaimed instance!
} ,
// we're expecting the new component to be created
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.self[\"added\"]" ) ,
PlanComplete : true ,
PlanApplyable : true ,
Action : plans . Create ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "added" ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "added" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"input" : nil ,
"id" : nil ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.self[\"added\"].testing_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"id" : cty . String ,
"value" : cty . String ,
} ) ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "added" ) ,
"value" : cty . StringVal ( "added" ) ,
} ) ) ,
} ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.self[\"removed\"]" ) ,
PlanComplete : true ,
PlanApplyable : true ,
Mode : plans . DestroyMode ,
Action : plans . Delete ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "removed" ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "removed" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"input" : nil ,
"id" : nil ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.self[\"removed\"].testing_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Delete ,
Before : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "removed" ) ,
"value" : cty . StringVal ( "removed" ) ,
} ) ) ,
After : mustPlanDynamicValue ( cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"id" : cty . String ,
"value" : cty . String ,
} ) ) ) ,
} ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
} ,
PriorStateSrc : & states . ResourceInstanceObjectSrc {
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "removed" ,
"value" : "removed" ,
} ) ,
Dependencies : make ( [ ] addrs . ConfigResource , 0 ) ,
Status : states . ObjectReady ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "input" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . SetVal ( [ ] cty . Value {
2024-09-07 08:54:32 -04:00
cty . StringVal ( "added" ) ,
} ) ,
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "removed" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . SetVal ( [ ] cty . Value {
2024-09-07 08:54:32 -04:00
cty . StringVal ( "removed" ) ,
} ) ,
} ,
} ,
wantPlanDiags : [ ] expectedDiagnostic {
{
severity : tfdiags . Error ,
summary : "Unclaimed component instance" ,
detail : "The component instance component.self[\"orphaned\"] is not claimed by any component or removed block in the configuration. Make sure it is instantiated by a component block, or targeted for removal by a removed block." ,
} ,
} ,
} ,
2024-09-10 02:50:09 -04:00
"duplicate component" : {
source : filepath . Join ( "with-single-input" , "removed-component-instance" ) ,
initialState : stackstate . NewStateBuilder ( ) .
AddComponentInstance ( stackstate . NewComponentInstanceBuilder ( mustAbsComponentInstance ( "component.self[\"a\"]" ) ) .
AddInputVariable ( "id" , cty . StringVal ( "a" ) ) .
AddInputVariable ( "input" , cty . StringVal ( "a" ) ) ) .
AddResourceInstance ( stackstate . NewResourceInstanceBuilder ( ) .
SetAddr ( mustAbsResourceInstanceObject ( "component.self[\"a\"].testing_resource.data" ) ) .
SetProviderAddr ( mustDefaultRootProvider ( "testing" ) ) .
SetResourceInstanceObjectSrc ( states . ResourceInstanceObjectSrc {
Status : states . ObjectReady ,
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "a" ,
"value" : "a" ,
} ) ,
} ) ) .
Build ( ) ,
store : stacks_testing_provider . NewResourceStoreBuilder ( ) .
AddResource ( "a" , cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "a" ) ,
"value" : cty . StringVal ( "a" ) ,
} ) ) .
Build ( ) ,
inputs : map [ string ] cty . Value {
"input" : cty . SetVal ( [ ] cty . Value {
cty . StringVal ( "a" ) ,
} ) ,
"removed" : cty . SetVal ( [ ] cty . Value {
cty . StringVal ( "a" ) ,
} ) ,
} ,
wantPlanChanges : [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : false , // No! The removed block is a duplicate of the component!
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : mustAbsComponentInstance ( "component.self[\"a\"]" ) ,
PlanComplete : true ,
PlanApplyable : false , // no changes
Action : plans . Update ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"id" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "a" ) ) ,
"input" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "a" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks {
"input" : nil ,
"id" : nil ,
} ,
PlannedOutputValues : make ( map [ string ] cty . Value ) ,
PlannedCheckResults : & states . CheckResults { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.self[\"a\"].testing_resource.data" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource.data" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource.data" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . NoOp ,
Before : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "a" ) ,
"value" : cty . StringVal ( "a" ) ,
} ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "a" ) ,
"value" : cty . StringVal ( "a" ) ,
} ) ) ,
} ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
} ,
PriorStateSrc : & states . ResourceInstanceObjectSrc {
AttrsJSON : mustMarshalJSONAttrs ( map [ string ] any {
"id" : "a" ,
"value" : "a" ,
} ) ,
Dependencies : make ( [ ] addrs . ConfigResource , 0 ) ,
Status : states . ObjectReady ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceSchema ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "input" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . SetVal ( [ ] cty . Value {
2024-09-10 02:50:09 -04:00
cty . StringVal ( "a" ) ,
} ) ,
} ,
& stackplan . PlannedChangeRootInputValue {
2024-09-16 05:45:19 -04:00
Addr : stackaddrs . InputVariable { Name : "removed" } ,
Action : plans . Create ,
Before : cty . NullVal ( cty . DynamicPseudoType ) ,
After : cty . SetVal ( [ ] cty . Value {
2024-09-10 02:50:09 -04:00
cty . StringVal ( "a" ) ,
} ) ,
} ,
} ,
wantPlanDiags : [ ] expectedDiagnostic {
{
severity : tfdiags . Error ,
summary : "Cannot remove component instance" ,
2025-05-15 02:33:13 -04:00
detail : "The component instance component.self[\"a\"] is targeted by a component block and cannot be removed. The relevant component is defined at git::https://example.com/test.git//with-single-input/removed-component-instance/removed-component-instance.tfcomponent.hcl:18,1-17." ,
2024-09-10 02:50:09 -04:00
} ,
} ,
} ,
2024-09-07 08:54:32 -04:00
}
for name , tc := range tcs {
t . Run ( name , func ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , tc . source )
inputs := make ( map [ stackaddrs . InputVariable ] ExternalInputValue , len ( tc . inputs ) )
for name , input := range tc . inputs {
inputs [ stackaddrs . InputVariable { Name : name } ] = ExternalInputValue {
Value : input ,
}
}
providers := map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
return stacks_testing_provider . NewProviderWithData ( t , tc . store ) , nil
} ,
}
planChangesCh := make ( chan stackplan . PlannedChange )
planDiagsCh := make ( chan tfdiags . Diagnostic )
planReq := PlanRequest {
Config : cfg ,
ProviderFactories : providers ,
InputValues : inputs ,
ForcePlanTimestamp : & fakePlanTimestamp ,
PrevState : tc . initialState ,
DependencyLocks : * lock ,
}
planResp := PlanResponse {
PlannedChanges : planChangesCh ,
Diagnostics : planDiagsCh ,
}
go Plan ( ctx , & planReq , & planResp )
gotPlanChanges , gotPlanDiags := collectPlanOutput ( planChangesCh , planDiagsCh )
sort . SliceStable ( gotPlanChanges , func ( i , j int ) bool {
return plannedChangeSortKey ( gotPlanChanges [ i ] ) < plannedChangeSortKey ( gotPlanChanges [ j ] )
} )
sort . SliceStable ( gotPlanDiags , diagnosticSortFunc ( gotPlanDiags ) )
expectDiagnosticsForTest ( t , gotPlanDiags , tc . wantPlanDiags ... )
2025-05-16 04:10:47 -04:00
if diff := cmp . Diff ( tc . wantPlanChanges , gotPlanChanges , changesCmpOpts ) ; diff != "" {
2024-09-07 08:54:32 -04:00
t . Errorf ( "wrong changes\n%s" , diff )
}
} )
}
}
2025-03-11 09:33:32 -04:00
func TestPlanWithResourceIdentities ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "resource-identity" )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
req := PlanRequest {
Config : cfg ,
ForcePlanTimestamp : & fakePlanTimestamp ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
return stacks_testing_provider . NewProvider ( t ) , nil
} ,
} ,
DependencyLocks : * lock ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
if len ( diags ) != 0 {
t . Errorf ( "unexpected diagnostics\n%s" , diags . ErrWithWarnings ( ) . Error ( ) )
}
wantChanges := [ ] stackplan . PlannedChange {
& stackplan . PlannedChangeApplyable {
Applyable : true ,
} ,
& stackplan . PlannedChangeComponentInstance {
Addr : stackaddrs . Absolute (
stackaddrs . RootStackInstance ,
stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "self" } ,
} ,
) ,
Action : plans . Create ,
PlanApplyable : true ,
PlanComplete : true ,
PlannedCheckResults : & states . CheckResults { } ,
PlannedInputValues : map [ string ] plans . DynamicValue {
"name" : mustPlanDynamicValueDynamicType ( cty . StringVal ( "example" ) ) ,
} ,
PlannedInputValueMarks : map [ string ] [ ] cty . PathValueMarks { "name" : nil } ,
PlannedOutputValues : map [ string ] cty . Value { } ,
PlanTimestamp : fakePlanTimestamp ,
} ,
& stackplan . PlannedChangeResourceInstancePlanned {
ResourceInstanceObjectAddr : mustAbsResourceInstanceObject ( "component.self.testing_resource_with_identity.hello" ) ,
ChangeSrc : & plans . ResourceInstanceChangeSrc {
Addr : mustAbsResourceInstance ( "testing_resource_with_identity.hello" ) ,
PrevRunAddr : mustAbsResourceInstance ( "testing_resource_with_identity.hello" ) ,
ProviderAddr : mustDefaultRootProvider ( "testing" ) ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : mustPlanDynamicValue ( cty . NullVal ( cty . DynamicPseudoType ) ) ,
After : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "example" ) ,
"value" : cty . NullVal ( cty . String ) ,
} ) ) ,
AfterIdentity : mustPlanDynamicValue ( cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "id:example" ) ,
} ) ) ,
} ,
} ,
ProviderConfigAddr : mustDefaultRootProvider ( "testing" ) ,
Schema : stacks_testing_provider . TestingResourceWithIdentitySchema ,
} ,
& stackplan . PlannedChangeHeader {
TerraformVersion : version . SemVer ,
} ,
& stackplan . PlannedChangePlannedTimestamp {
PlannedTimestamp : fakePlanTimestamp ,
} ,
}
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
} )
if diff := cmp . Diff ( wantChanges , gotChanges , changesCmpOpts ) ; diff != "" {
t . Errorf ( "wrong changes\n%s" , diff )
}
}
2025-12-08 09:52:23 -05:00
func TestPlanInvalidLocalValue ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "invalid-local" )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
changesCh := make ( chan stackplan . PlannedChange , 8 )
diagsCh := make ( chan tfdiags . Diagnostic , 2 )
req := PlanRequest {
Config : cfg ,
ForcePlanTimestamp : & fakePlanTimestamp ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
return stacks_testing_provider . NewProvider ( t ) , nil
} ,
} ,
DependencyLocks : * lock ,
InputValues : map [ stackaddrs . InputVariable ] ExternalInputValue {
{ Name : "in" } : {
Value : cty . ObjectVal ( map [ string ] cty . Value { "name" : cty . StringVal ( "foo" ) } ) ,
} ,
} ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
tfdiags . AssertDiagnosticsMatch ( t , diags , tfdiags . Diagnostics { } . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid operand" ,
Detail : "Unsuitable value for left operand: a number is required." ,
Subject : & hcl . Range {
Filename : "git::https://example.com/test.git//invalid-local/invalid-local.tfcomponent.hcl" ,
Start : hcl . Pos { Line : 19 , Column : 49 , Byte : 377 } ,
End : hcl . Pos { Line : 19 , Column : 50 , Byte : 378 } ,
} ,
Context : & hcl . Range {
Filename : "git::https://example.com/test.git//invalid-local/invalid-local.tfcomponent.hcl" ,
Start : hcl . Pos { Line : 19 , Column : 49 , Byte : 377 } ,
End : hcl . Pos { Line : 19 , Column : 54 , Byte : 382 } ,
} ,
} ) )
// We don't really care about the precise content of the plan changes here,
// we just want to ensure that the produced plan is not applyable
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
} )
pca , ok := gotChanges [ 0 ] . ( * stackplan . PlannedChangeApplyable )
if ! ok {
t . Fatalf ( "expected first change to be PlannedChangeApplyable, got %T" , gotChanges [ 0 ] )
}
if pca . Applyable {
t . Fatalf ( "expected plan to be not applyable due to invalid local value, but it is applyable" )
}
}
2024-08-12 09:02:36 -04:00
// collectPlanOutput consumes the two output channels emitting results from
// a call to [Plan], and collects all of the data written to them before
// returning once changesCh has been closed by the sender to indicate that
// the planning process is complete.
func collectPlanOutput ( changesCh <- chan stackplan . PlannedChange , diagsCh <- chan tfdiags . Diagnostic ) ( [ ] stackplan . PlannedChange , tfdiags . Diagnostics ) {
var changes [ ] stackplan . PlannedChange
var diags tfdiags . Diagnostics
for {
select {
case change , ok := <- changesCh :
if ! ok {
// The plan operation is complete but we might still have
// some buffered diagnostics to consume.
if diagsCh != nil {
for diag := range diagsCh {
diags = append ( diags , diag )
}
}
return changes , diags
}
changes = append ( changes , change )
case diag , ok := <- diagsCh :
if ! ok {
// no more diagnostics to read
diagsCh = nil
continue
}
diags = append ( diags , diag )
}
}
}
2024-06-20 07:10:23 -04:00
func expectOutput ( t * testing . T , name string , changes [ ] stackplan . PlannedChange ) * stackplan . PlannedChangeOutputValue {
t . Helper ( )
for _ , change := range changes {
if v , ok := change . ( * stackplan . PlannedChangeOutputValue ) ; ok && v . Addr . Name == name {
return v
}
}
t . Fatalf ( "expected output value %q" , name )
return nil
}
2026-02-02 10:38:27 -05:00
func TestPlanWithActionInvocationHooks ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "planning-action-lifecycle" )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1991-08-25T20:57:08Z" )
if err != nil {
t . Fatal ( err )
}
testCtx := TestContext {
config : cfg ,
providers : map [ addrs . Provider ] providers . Factory {
addrs . NewBuiltInProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
return stacks_testing_provider . NewProvider ( t ) , nil
} ,
} ,
timestamp : & fakePlanTimestamp ,
}
// Create dynamic values for resource change
resourceBeforeVal := cty . NullVal ( cty . Object ( map [ string ] cty . Type {
"id" : cty . String ,
"value" : cty . String ,
} ) )
resourceAfterVal := cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . UnknownVal ( cty . String ) ,
"value" : cty . StringVal ( "example" ) ,
} )
resourceBeforeDynVal , err := plans . NewDynamicValue ( resourceBeforeVal , resourceBeforeVal . Type ( ) )
if err != nil {
t . Fatal ( err )
}
resourceAfterDynVal , err := plans . NewDynamicValue ( resourceAfterVal , resourceAfterVal . Type ( ) )
if err != nil {
t . Fatal ( err )
}
// Common addresses used throughout the test
webComponentInstance := stackaddrs . AbsComponentInstance {
Stack : stackaddrs . RootStackInstance ,
Item : stackaddrs . ComponentInstance {
Component : stackaddrs . Component { Name : "web" } ,
} ,
}
webComponent := stackaddrs . AbsComponent {
Stack : stackaddrs . RootStackInstance ,
Item : stackaddrs . Component { Name : "web" } ,
}
testResourceInstance := addrs . RootModuleInstance . ResourceInstance ( addrs . ManagedResourceMode , "testing_resource" , "main" , addrs . NoKey )
testResourceObject := stackaddrs . AbsResourceInstanceObject {
Component : webComponentInstance ,
Item : addrs . AbsResourceInstanceObject {
ResourceInstance : testResourceInstance ,
} ,
}
testActionInstance := addrs . RootModuleInstance . ActionInstance ( "testing_action" , "notify" , addrs . NoKey )
testActionInvocationAddr := stackaddrs . AbsActionInvocationInstance {
Component : webComponentInstance ,
Item : testActionInstance ,
}
testProviderConfig := addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . NewBuiltInProvider ( "testing" ) ,
}
expectedHooks := ExpectedHooks {
ReportActionInvocationPlanned : [ ] * hooks . ActionInvocation {
{
Addr : testActionInvocationAddr ,
ProviderAddr : addrs . NewBuiltInProvider ( "testing" ) ,
2026-03-03 08:18:38 -05:00
Trigger : & plans . ResourceActionTrigger {
2026-02-02 10:38:27 -05:00
TriggeringResourceAddr : testResourceInstance ,
ActionTriggerEvent : configs . AfterCreate ,
ActionTriggerBlockIndex : 0 ,
ActionsListIndex : 0 ,
} ,
} ,
} ,
ComponentExpanded : [ ] * hooks . ComponentInstances {
{
ComponentAddr : webComponent ,
InstanceAddrs : [ ] stackaddrs . AbsComponentInstance { webComponentInstance } ,
} ,
} ,
2026-02-13 07:30:08 -05:00
PendingComponentInstancePlan : collections . NewSet ( webComponentInstance ) ,
BeginComponentInstancePlan : collections . NewSet ( webComponentInstance ) ,
EndComponentInstancePlan : collections . NewSet ( webComponentInstance ) ,
2026-02-02 10:38:27 -05:00
ReportResourceInstanceStatus : [ ] * hooks . ResourceInstanceStatusHookData {
{
Addr : testResourceObject ,
ProviderAddr : addrs . NewBuiltInProvider ( "testing" ) ,
Status : hooks . ResourceInstancePlanning ,
} ,
{
Addr : testResourceObject ,
ProviderAddr : addrs . NewBuiltInProvider ( "testing" ) ,
Status : hooks . ResourceInstancePlanned ,
} ,
} ,
ReportResourceInstancePlanned : [ ] * hooks . ResourceInstanceChange {
{
Addr : testResourceObject ,
Change : & plans . ResourceInstanceChangeSrc {
Addr : testResourceInstance ,
PrevRunAddr : testResourceInstance ,
ProviderAddr : testProviderConfig ,
ChangeSrc : plans . ChangeSrc {
Action : plans . Create ,
Before : resourceBeforeDynVal ,
After : resourceAfterDynVal ,
} ,
} ,
} ,
} ,
ReportComponentInstancePlanned : [ ] * hooks . ComponentInstanceChange {
{
Addr : webComponentInstance ,
Add : 1 ,
ActionInvocation : 1 ,
} ,
} ,
}
cycle := TestCycle {
planMode : plans . NormalMode ,
wantPlannedHooks : & expectedHooks ,
}
testCtx . Plan ( t , ctx , stackstate . NewState ( ) , cycle )
}
2026-02-03 08:31:42 -05:00
func TestPlanWithDeferredActionInvocation ( t * testing . T ) {
ctx := context . Background ( )
cfg := loadMainBundleConfigForTest ( t , "deferred-action" )
fakePlanTimestamp , err := time . Parse ( time . RFC3339 , "1994-09-05T08:50:00Z" )
if err != nil {
t . Fatal ( err )
}
changesCh := make ( chan stackplan . PlannedChange )
diagsCh := make ( chan tfdiags . Diagnostic )
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
req := PlanRequest {
Config : cfg ,
ProviderFactories : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
return stacks_testing_provider . NewProvider ( t ) , nil
} ,
} ,
DependencyLocks : * lock ,
ForcePlanTimestamp : & fakePlanTimestamp ,
InputValues : map [ stackaddrs . InputVariable ] ExternalInputValue {
{ Name : "id" } : {
Value : cty . StringVal ( "test-id-123" ) ,
} ,
{ Name : "defer" } : {
Value : cty . BoolVal ( true ) ,
} ,
} ,
}
resp := PlanResponse {
PlannedChanges : changesCh ,
Diagnostics : diagsCh ,
}
go Plan ( ctx , & req , & resp )
gotChanges , diags := collectPlanOutput ( changesCh , diagsCh )
reportDiagnosticsForTest ( t , diags )
if len ( diags ) != 0 {
t . FailNow ( ) // We reported the diags above
}
sort . SliceStable ( gotChanges , func ( i , j int ) bool {
return plannedChangeSortKey ( gotChanges [ i ] ) < plannedChangeSortKey ( gotChanges [ j ] )
} )
2026-02-03 08:31:42 -05:00
// Find the deferred action invocation in the changes
2026-02-03 08:31:42 -05:00
var foundDeferredAction bool
for _ , change := range gotChanges {
2026-02-03 08:31:42 -05:00
if _ , ok := change . ( * stackplan . PlannedChangeDeferredActionInvocation ) ; ok {
2026-02-03 08:31:42 -05:00
foundDeferredAction = true
2026-02-03 08:31:42 -05:00
break
2026-02-03 08:31:42 -05:00
}
}
if ! foundDeferredAction {
t . Error ( "Expected to find a deferred action invocation in the plan changes, but none was found" )
t . Logf ( "Got %d changes:" , len ( gotChanges ) )
for i , change := range gotChanges {
t . Logf ( " [%d] %T" , i , change )
}
}
}