| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package stackruntime | |
| import ( | |
| "context" | |
| "path" | |
| "path/filepath" | |
| "strconv" | |
| "testing" | |
| "time" | |
| "github.com/hashicorp/hcl/v2" | |
| "github.com/zclconf/go-cty/cty" | |
| "github.com/hashicorp/terraform/internal/addrs" | |
| "github.com/hashicorp/terraform/internal/collections" | |
| "github.com/hashicorp/terraform/internal/depsfile" | |
| "github.com/hashicorp/terraform/internal/getproviders/providerreqs" | |
| "github.com/hashicorp/terraform/internal/plans" | |
| "github.com/hashicorp/terraform/internal/providers" | |
| "github.com/hashicorp/terraform/internal/stacks/stackaddrs" | |
| "github.com/hashicorp/terraform/internal/stacks/stackplan" | |
| "github.com/hashicorp/terraform/internal/stacks/stackruntime/hooks" | |
| stacks_testing_provider "github.com/hashicorp/terraform/internal/stacks/stackruntime/testing" | |
| "github.com/hashicorp/terraform/internal/stacks/stackstate" | |
| "github.com/hashicorp/terraform/internal/states" | |
| "github.com/hashicorp/terraform/internal/tfdiags" | |
| "github.com/hashicorp/terraform/version" | |
| ) | |
| func TestApplyDestroy(t *testing.T) { | |
| fakePlanTimestamp, err := time.Parse(time.RFC3339, "2021-01-01T00:00:00Z") | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| tcs := map[string]struct { | |
| path string | |
| description string | |
| state *stackstate.State | |
| store *stacks_testing_provider.ResourceStore | |
| mutators []func(*stacks_testing_provider.ResourceStore, TestContext) TestContext | |
| cycles []TestCycle | |
| }{ | |
| "inputs-and-outputs": { | |
| path: "component-input-output", | |
| state: stackstate.NewStateBuilder(). | |
| AddInput("value", cty.StringVal("foo")). | |
| AddOutput("value", cty.StringVal("foo")). | |
| Build(), | |
| cycles: []TestCycle{ | |
| { | |
| planMode: plans.DestroyMode, | |
| planInputs: map[string]cty.Value{ | |
| "value": cty.StringVal("foo"), | |
| }, | |
| wantPlannedChanges: []stackplan.PlannedChange{ | |
| &stackplan.PlannedChangeApplyable{ | |
| Applyable: true, | |
| }, | |
| &stackplan.PlannedChangeHeader{ | |
| TerraformVersion: version.SemVer, | |
| }, | |
| &stackplan.PlannedChangeOutputValue{ | |
| Addr: mustStackOutputValue("value"), | |
| Action: plans.Delete, | |
| Before: cty.StringVal("foo"), | |
| After: cty.NullVal(cty.String), | |
| }, | |
| &stackplan.PlannedChangePlannedTimestamp{ | |
| PlannedTimestamp: fakePlanTimestamp, | |
| }, | |
| &stackplan.PlannedChangeRootInputValue{ | |
| Addr: mustStackInputVariable("value"), | |
| Action: plans.NoOp, | |
| Before: cty.StringVal("foo"), | |
| After: cty.StringVal("foo"), | |
| DeleteOnApply: true, | |
| }, | |
| }, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeOutputValue{ | |
| Addr: mustStackOutputValue("value"), | |
| Value: cty.NilVal, // destroyed | |
| }, | |
| &stackstate.AppliedChangeInputVariable{ | |
| Addr: mustStackInputVariable("value"), | |
| Value: cty.NilVal, // destroyed | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "missing-resource": { | |
| path: path.Join("with-single-input", "valid"), | |
| description: "tests what happens when a resource is in state but not in the provider", | |
| state: stackstate.NewStateBuilder(). | |
| AddComponentInstance(stackstate.NewComponentInstanceBuilder(mustAbsComponentInstance("component.self")). | |
| AddInputVariable("id", cty.StringVal("e84b59f2")). | |
| AddInputVariable("value", cty.StringVal("hello"))). | |
| AddResourceInstance(stackstate.NewResourceInstanceBuilder(). | |
| SetAddr(mustAbsResourceInstanceObject("component.self.testing_resource.data")). | |
| SetProviderAddr(mustDefaultRootProvider("testing")). | |
| SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{ | |
| SchemaVersion: 0, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "e84b59f2", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| })). | |
| Build(), | |
| cycles: []TestCycle{ | |
| { | |
| planMode: plans.DestroyMode, | |
| planInputs: map[string]cty.Value{ | |
| "input": cty.StringVal("hello"), | |
| }, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.self"), | |
| }, | |
| // The resource that was in state but not in the data store should still | |
| // be included to be destroyed. | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| NewStateSrc: nil, // We should be removing this from the state file. | |
| Schema: providers.Schema{}, | |
| }, | |
| &stackstate.AppliedChangeInputVariable{ | |
| Addr: mustStackInputVariable("id"), | |
| Value: cty.NilVal, // destroyed | |
| }, | |
| &stackstate.AppliedChangeInputVariable{ | |
| Addr: mustStackInputVariable("input"), | |
| Value: cty.NilVal, // destroyed | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "datasource-in-state": { | |
| path: "with-data-source", | |
| description: "tests that we emit removal notices for data sources", | |
| store: stacks_testing_provider.NewResourceStoreBuilder(). | |
| AddResource("foo", cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("foo"), | |
| "value": cty.StringVal("hello"), | |
| })).Build(), | |
| state: stackstate.NewStateBuilder(). | |
| AddResourceInstance(stackstate.NewResourceInstanceBuilder(). | |
| SetAddr(mustAbsResourceInstanceObject("component.self.data.testing_data_source.missing")). | |
| SetProviderAddr(mustDefaultRootProvider("testing")). | |
| SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{ | |
| SchemaVersion: 0, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "e84b59f2", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| })). | |
| Build(), | |
| cycles: []TestCycle{ | |
| { | |
| planMode: plans.DestroyMode, | |
| planInputs: map[string]cty.Value{ | |
| "id": cty.StringVal("foo"), | |
| "resource": cty.StringVal("bar"), | |
| }, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.self"), | |
| }, | |
| // This is a bit of a quirk of the system, this wasn't in the state | |
| // file before so we don't need to emit this. But since Terraform | |
| // pushes data sources into the refresh state, it's very difficult to | |
| // tell the difference between this kind of change that doesn't need to | |
| // be emitted, and the next change that does need to be emitted. It's | |
| // better to emit both than to miss one, and emitting this doesn't | |
| // actually harm anything. | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.data.testing_data_source.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: providers.Schema{}, | |
| NewStateSrc: nil, // deleted | |
| }, | |
| // This was in the state file, so we're emitting the destroy notice. | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.data.testing_data_source.missing"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: providers.Schema{}, | |
| NewStateSrc: nil, | |
| }, | |
| &stackstate.AppliedChangeInputVariable{ | |
| Addr: mustStackInputVariable("id"), | |
| Value: cty.NilVal, // destroyed | |
| }, | |
| &stackstate.AppliedChangeInputVariable{ | |
| Addr: mustStackInputVariable("resource"), | |
| Value: cty.NilVal, // destroyed | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "orphaned-data-sources-removed": { | |
| path: "with-data-source", | |
| description: "tests that we emit removal notices for data sources that are no longer in the configuration", | |
| store: stacks_testing_provider.NewResourceStoreBuilder(). | |
| AddResource("foo", cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("foo"), | |
| "value": cty.StringVal("hello"), | |
| })).Build(), | |
| state: stackstate.NewStateBuilder(). | |
| AddResourceInstance(stackstate.NewResourceInstanceBuilder(). | |
| SetAddr(mustAbsResourceInstanceObject("component.self.data.testing_data_source.missing")). | |
| SetProviderAddr(mustDefaultRootProvider("testing")). | |
| SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{ | |
| SchemaVersion: 0, | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "e84b59f2", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| })). | |
| Build(), | |
| cycles: []TestCycle{ | |
| { | |
| planMode: plans.NormalMode, | |
| planInputs: map[string]cty.Value{ | |
| "id": cty.StringVal("foo"), | |
| "resource": cty.StringVal("bar"), | |
| }, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.self"), | |
| OutputValues: make(map[addrs.OutputValue]cty.Value), | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| mustInputVariable("id"): cty.StringVal("foo"), | |
| mustInputVariable("resource"): cty.StringVal("bar"), | |
| }, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.data.testing_data_source.data"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "foo", | |
| "value": "hello", | |
| }), | |
| AttrSensitivePaths: make([]cty.Path, 0), | |
| Status: states.ObjectReady, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingDataSourceSchema, | |
| }, | |
| // This data source should be removed from the state file as it is no | |
| // longer in the configuration. | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.data.testing_data_source.missing"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: providers.Schema{}, | |
| NewStateSrc: nil, // deleted | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.data"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "bar", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Dependencies: []addrs.ConfigResource{ | |
| mustAbsResourceInstance("data.testing_data_source.data").ConfigResource(), | |
| }, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeInputVariable{ | |
| Addr: mustStackInputVariable("id"), | |
| Value: cty.StringVal("foo"), | |
| }, | |
| &stackstate.AppliedChangeInputVariable{ | |
| Addr: mustStackInputVariable("resource"), | |
| Value: cty.StringVal("bar"), | |
| }, | |
| }, | |
| }, | |
| { | |
| planMode: plans.DestroyMode, | |
| planInputs: map[string]cty.Value{ | |
| "id": cty.StringVal("foo"), | |
| "resource": cty.StringVal("bar"), | |
| }, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.self"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.data.testing_data_source.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: providers.Schema{}, | |
| NewStateSrc: nil, // deleted | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: providers.Schema{}, | |
| NewStateSrc: nil, // deleted | |
| }, | |
| &stackstate.AppliedChangeInputVariable{ | |
| Addr: mustStackInputVariable("id"), | |
| Value: cty.NilVal, // destroyed | |
| }, | |
| &stackstate.AppliedChangeInputVariable{ | |
| Addr: mustStackInputVariable("resource"), | |
| Value: cty.NilVal, // destroyed | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "dependent-resources": { | |
| path: "dependent-component", | |
| description: "test the order of operations during create and destroy", | |
| cycles: []TestCycle{ | |
| { | |
| planMode: plans.NormalMode, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.self"), | |
| Dependencies: collections.NewSet(mustAbsComponent("component.valid")), | |
| OutputValues: make(map[addrs.OutputValue]cty.Value), | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| mustInputVariable("id"): cty.StringVal("dependent"), | |
| mustInputVariable("requirements"): cty.SetVal([]cty.Value{ | |
| cty.StringVal("valid"), | |
| }), | |
| }, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_blocked_resource.resource"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "dependent", | |
| "value": nil, | |
| "required_resources": []interface{}{"valid"}, | |
| }), | |
| Status: states.ObjectReady, | |
| Dependencies: make([]addrs.ConfigResource, 0), | |
| }, | |
| Schema: stacks_testing_provider.BlockedResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.valid"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.valid"), | |
| Dependents: collections.NewSet(mustAbsComponent("component.self")), | |
| OutputValues: make(map[addrs.OutputValue]cty.Value), | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| mustInputVariable("id"): cty.StringVal("valid"), | |
| mustInputVariable("input"): cty.StringVal("resource"), | |
| }, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.valid.testing_resource.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "valid", | |
| "value": "resource", | |
| }), | |
| Status: states.ObjectReady, | |
| Dependencies: make([]addrs.ConfigResource, 0), | |
| }, | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| }, | |
| }, | |
| { | |
| planMode: plans.DestroyMode, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.self"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_blocked_resource.resource"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.valid"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.valid"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.valid.testing_resource.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "failed-destroy": { | |
| path: "failed-component", | |
| description: "tests what happens if a component fails to destroy", | |
| state: stackstate.NewStateBuilder(). | |
| AddComponentInstance(stackstate.NewComponentInstanceBuilder(mustAbsComponentInstance("component.self"))). | |
| AddResourceInstance(stackstate.NewResourceInstanceBuilder(). | |
| SetAddr(mustAbsResourceInstanceObject("component.self.testing_failed_resource.data")). | |
| SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "failed", | |
| "value": "resource", | |
| "fail_plan": false, | |
| "fail_apply": true, | |
| }), | |
| Status: states.ObjectReady, | |
| }). | |
| SetProviderAddr(mustDefaultRootProvider("testing"))). | |
| Build(), | |
| store: stacks_testing_provider.NewResourceStoreBuilder(). | |
| AddResource("failed", cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("failed"), | |
| "value": cty.StringVal("resource"), | |
| "fail_plan": cty.False, | |
| "fail_apply": cty.True, | |
| })). | |
| Build(), | |
| cycles: []TestCycle{ | |
| { | |
| planMode: plans.DestroyMode, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.self"), | |
| OutputValues: make(map[addrs.OutputValue]cty.Value), | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| mustInputVariable("id"): cty.StringVal("failed"), | |
| mustInputVariable("input"): cty.StringVal("resource"), | |
| mustInputVariable("fail_plan"): cty.False, | |
| mustInputVariable("fail_apply"): cty.False, | |
| }, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_failed_resource.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "failed", | |
| "value": "resource", | |
| "fail_plan": false, | |
| "fail_apply": true, | |
| }), | |
| Status: states.ObjectReady, | |
| Dependencies: make([]addrs.ConfigResource, 0), | |
| }, | |
| Schema: stacks_testing_provider.FailedResourceSchema, | |
| }, | |
| &stackstate.AppliedChangeInputVariable{ | |
| Addr: mustStackInputVariable("fail_apply"), | |
| Value: cty.NilVal, // destroyed | |
| }, | |
| &stackstate.AppliedChangeInputVariable{ | |
| Addr: mustStackInputVariable("fail_plan"), | |
| Value: cty.NilVal, // destroyed | |
| }, | |
| }, | |
| wantAppliedDiags: initDiags(func(diags tfdiags.Diagnostics) tfdiags.Diagnostics { | |
| return diags.Append(&hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "failedResource error", | |
| Detail: "failed during apply", | |
| }) | |
| }), | |
| }, | |
| }, | |
| }, | |
| "destroy-after-failed-apply": { | |
| path: path.Join("with-single-input", "failed-child"), | |
| description: "tests destroying when state is only partially applied", | |
| cycles: []TestCycle{ | |
| { | |
| planMode: plans.NormalMode, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.child"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.child"), | |
| Dependencies: collections.NewSet(mustAbsComponent("component.self")), | |
| OutputValues: make(map[addrs.OutputValue]cty.Value), | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| mustInputVariable("id"): cty.NullVal(cty.String), | |
| mustInputVariable("input"): cty.StringVal("child"), | |
| mustInputVariable("fail_plan"): cty.NullVal(cty.Bool), | |
| mustInputVariable("fail_apply"): cty.True, | |
| }, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.child.testing_failed_resource.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.self"), | |
| Dependents: collections.NewSet(mustAbsComponent("component.child")), | |
| OutputValues: make(map[addrs.OutputValue]cty.Value), | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| mustInputVariable("id"): cty.StringVal("self"), | |
| mustInputVariable("input"): cty.StringVal("value"), | |
| }, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "self", | |
| "value": "value", | |
| }), | |
| Status: states.ObjectReady, | |
| Dependencies: make([]addrs.ConfigResource, 0), | |
| }, | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| }, | |
| wantAppliedDiags: initDiags(func(diags tfdiags.Diagnostics) tfdiags.Diagnostics { | |
| return diags.Append(&hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "failedResource error", | |
| Detail: "failed during apply", | |
| }) | |
| }), | |
| }, | |
| { | |
| planMode: plans.DestroyMode, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.child"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.child"), | |
| }, | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.self"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "destroy-after-deferred-apply": { | |
| path: "deferred-dependent", | |
| description: "tests what happens when a destroy plan is applied after components have been deferred", | |
| cycles: []TestCycle{ | |
| { | |
| planMode: plans.NormalMode, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.deferred"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.deferred"), | |
| Dependencies: collections.NewSet(mustAbsComponent("component.valid")), | |
| OutputValues: make(map[addrs.OutputValue]cty.Value), | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| mustInputVariable("id"): cty.StringVal("deferred"), | |
| mustInputVariable("defer"): cty.True, | |
| }, | |
| }, | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.valid"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.valid"), | |
| Dependents: collections.NewSet(mustAbsComponent("component.deferred")), | |
| OutputValues: make(map[addrs.OutputValue]cty.Value), | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| mustInputVariable("id"): cty.StringVal("valid"), | |
| mustInputVariable("input"): cty.StringVal("valid"), | |
| }, | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.valid.testing_resource.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| NewStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "valid", | |
| "value": "valid", | |
| }), | |
| Status: states.ObjectReady, | |
| Dependencies: make([]addrs.ConfigResource, 0), | |
| }, | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| }, | |
| }, | |
| { | |
| planMode: plans.DestroyMode, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.deferred"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.deferred"), | |
| }, | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.valid"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.valid"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.valid.testing_resource.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "deferred-destroy": { | |
| path: "deferred-dependent", | |
| description: "tests what happens when a destroy operation is deferred", | |
| state: stackstate.NewStateBuilder(). | |
| AddComponentInstance(stackstate.NewComponentInstanceBuilder(mustAbsComponentInstance("component.valid")). | |
| AddDependent(mustAbsComponent("component.deferred"))). | |
| AddResourceInstance(stackstate.NewResourceInstanceBuilder(). | |
| SetAddr(mustAbsResourceInstanceObject("component.valid.testing_resource.data")). | |
| SetProviderAddr(mustDefaultRootProvider("testing")). | |
| SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "valid", | |
| "value": "valid", | |
| }), | |
| Status: states.ObjectReady, | |
| })). | |
| AddComponentInstance(stackstate.NewComponentInstanceBuilder(mustAbsComponentInstance("component.deferred")). | |
| AddDependency(mustAbsComponent("component.valid"))). | |
| AddResourceInstance(stackstate.NewResourceInstanceBuilder(). | |
| SetAddr(mustAbsResourceInstanceObject("component.deferred.testing_deferred_resource.data")). | |
| SetProviderAddr(mustDefaultRootProvider("testing")). | |
| SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "deferred", | |
| "value": nil, | |
| "deferred": true, | |
| }), | |
| Status: states.ObjectReady, | |
| })). | |
| Build(), | |
| store: stacks_testing_provider.NewResourceStoreBuilder(). | |
| AddResource("valid", cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("valid"), | |
| "value": cty.StringVal("valid"), | |
| })). | |
| AddResource("deferred", cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("deferred"), | |
| "value": cty.NullVal(cty.String), | |
| "deferred": cty.True, | |
| })). | |
| Build(), | |
| cycles: []TestCycle{ | |
| { | |
| planMode: plans.DestroyMode, | |
| wantPlannedChanges: []stackplan.PlannedChange{ | |
| &stackplan.PlannedChangeApplyable{ | |
| Applyable: true, | |
| }, | |
| &stackplan.PlannedChangeComponentInstance{ | |
| Addr: mustAbsComponentInstance("component.deferred"), | |
| Action: plans.Delete, | |
| Mode: plans.DestroyMode, | |
| RequiredComponents: collections.NewSet[stackaddrs.AbsComponent](mustAbsComponent("component.valid")), | |
| PlannedInputValues: map[string]plans.DynamicValue{ | |
| "id": mustPlanDynamicValueDynamicType(cty.StringVal("deferred")), | |
| "defer": mustPlanDynamicValueDynamicType(cty.True), | |
| }, | |
| PlannedInputValueMarks: map[string][]cty.PathValueMarks{ | |
| "id": nil, | |
| "defer": nil, | |
| }, | |
| PlannedOutputValues: make(map[string]cty.Value), | |
| PlannedCheckResults: &states.CheckResults{}, | |
| PlanTimestamp: fakePlanTimestamp, | |
| }, | |
| &stackplan.PlannedChangeDeferredResourceInstancePlanned{ | |
| ResourceInstancePlanned: stackplan.PlannedChangeResourceInstancePlanned{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.deferred.testing_deferred_resource.data"), | |
| ChangeSrc: &plans.ResourceInstanceChangeSrc{ | |
| Addr: mustAbsResourceInstance("testing_deferred_resource.data"), | |
| PrevRunAddr: mustAbsResourceInstance("testing_deferred_resource.data"), | |
| ProviderAddr: mustDefaultRootProvider("testing"), | |
| ChangeSrc: plans.ChangeSrc{ | |
| Action: plans.Delete, | |
| Before: mustPlanDynamicValue(cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("deferred"), | |
| "value": cty.NullVal(cty.String), | |
| "deferred": cty.True, | |
| })), | |
| After: mustPlanDynamicValue(cty.NullVal(cty.String)), | |
| }, | |
| }, | |
| PriorStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "deferred", | |
| "value": nil, | |
| "deferred": true, | |
| }), | |
| Status: states.ObjectReady, | |
| Dependencies: make([]addrs.ConfigResource, 0), | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.DeferredResourceSchema, | |
| }, | |
| DeferredReason: "resource_config_unknown", | |
| }, | |
| &stackplan.PlannedChangeComponentInstance{ | |
| Addr: mustAbsComponentInstance("component.valid"), | |
| PlanApplyable: false, | |
| Action: plans.Delete, | |
| Mode: plans.DestroyMode, | |
| PlannedInputValues: map[string]plans.DynamicValue{ | |
| "id": mustPlanDynamicValueDynamicType(cty.StringVal("valid")), | |
| "input": mustPlanDynamicValueDynamicType(cty.StringVal("valid")), | |
| }, | |
| 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("component.valid.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("valid"), | |
| "value": cty.StringVal("valid"), | |
| })), | |
| After: mustPlanDynamicValue(cty.NullVal(cty.String)), | |
| }, | |
| }, | |
| PriorStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "valid", | |
| "value": "valid", | |
| }), | |
| Status: states.ObjectReady, | |
| Dependencies: make([]addrs.ConfigResource, 0), | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| DeferredReason: "deferred_prereq", | |
| }, | |
| &stackplan.PlannedChangeHeader{ | |
| TerraformVersion: version.SemVer, | |
| }, | |
| &stackplan.PlannedChangePlannedTimestamp{ | |
| PlannedTimestamp: fakePlanTimestamp, | |
| }, | |
| }, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.deferred"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.deferred"), | |
| Dependencies: collections.NewSet(mustAbsComponent("component.valid")), | |
| OutputValues: make(map[addrs.OutputValue]cty.Value), | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| mustInputVariable("id"): cty.StringVal("deferred"), | |
| mustInputVariable("defer"): cty.True, | |
| }, | |
| }, | |
| &stackstate.AppliedChangeComponentInstance{ | |
| ComponentAddr: mustAbsComponent("component.valid"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.valid"), | |
| Dependents: collections.NewSet(mustAbsComponent("component.deferred")), | |
| OutputValues: make(map[addrs.OutputValue]cty.Value), | |
| InputVariables: map[addrs.InputVariable]cty.Value{ | |
| mustInputVariable("id"): cty.StringVal("valid"), | |
| mustInputVariable("input"): cty.StringVal("valid"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "destroy-with-input-dependency": { | |
| path: path.Join("with-single-input-and-output", "input-dependency"), | |
| description: "tests destroy operations with input dependencies", | |
| cycles: []TestCycle{ | |
| { | |
| // Just create everything normally, and don't validate it. | |
| planMode: plans.NormalMode, | |
| }, | |
| { | |
| planMode: plans.DestroyMode, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.child"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.child"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.child.testing_resource.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.parent"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.parent"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "destroy-with-provider-dependency": { | |
| path: path.Join("with-single-input-and-output", "provider-dependency"), | |
| description: "tests destroy operations with provider dependencies", | |
| cycles: []TestCycle{ | |
| { | |
| // Just create everything normally, and don't validate it. | |
| planMode: plans.NormalMode, | |
| }, | |
| { | |
| planMode: plans.DestroyMode, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.child"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.child"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.child.testing_resource.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.parent"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.parent"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "destroy-with-for-each-dependency": { | |
| path: path.Join("with-single-input-and-output", "for-each-dependency"), | |
| description: "tests destroy operations with for-each dependencies", | |
| cycles: []TestCycle{ | |
| { | |
| // Just create everything normally, and don't validate it. | |
| planMode: plans.NormalMode, | |
| }, | |
| { | |
| planMode: plans.DestroyMode, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.child"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.child[\"a\"]"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.child[\"a\"].testing_resource.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.parent"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.parent[\"a\"]"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent[\"a\"].testing_resource.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "destroy-with-provider-req": { | |
| path: "auth-provider-w-data", | |
| mutators: []func(store *stacks_testing_provider.ResourceStore, testContext TestContext) TestContext{ | |
| func(store *stacks_testing_provider.ResourceStore, testContext TestContext) TestContext { | |
| store.Set("credentials", cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("credentials"), | |
| "value": cty.StringVal("zero"), | |
| })) | |
| testContext.providers[addrs.NewDefaultProvider("testing")] = func() (providers.Interface, error) { | |
| provider := stacks_testing_provider.NewProviderWithData(t, store) | |
| provider.Authentication = "zero" | |
| return provider, nil | |
| } | |
| return testContext | |
| }, | |
| func(store *stacks_testing_provider.ResourceStore, testContext TestContext) TestContext { | |
| store.Set("credentials", cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("credentials"), | |
| "value": cty.StringVal("one"), | |
| })) | |
| testContext.providers[addrs.NewDefaultProvider("testing")] = func() (providers.Interface, error) { | |
| provider := stacks_testing_provider.NewProviderWithData(t, store) | |
| provider.Authentication = "one" // So we must reload the data source in order to authenticate. | |
| return provider, nil | |
| } | |
| return testContext | |
| }, | |
| }, | |
| cycles: []TestCycle{ | |
| { | |
| planMode: plans.NormalMode, | |
| }, | |
| { | |
| planMode: plans.DestroyMode, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.create"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.create"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.create.testing_resource.resource"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.load"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.load"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.load.data.testing_data_source.credentials"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "destroy-with-provider-req-and-removed": { | |
| path: path.Join("auth-provider-w-data", "removed"), | |
| state: stackstate.NewStateBuilder(). | |
| AddComponentInstance(stackstate.NewComponentInstanceBuilder(mustAbsComponentInstance("component.load")). | |
| AddDependent(mustAbsComponent("component.create")). | |
| AddOutputValue("credentials", cty.StringVal("wrong"))). // must reload the credentials | |
| AddComponentInstance(stackstate.NewComponentInstanceBuilder(mustAbsComponentInstance("component.create")). | |
| AddDependency(mustAbsComponent("component.load"))). | |
| AddResourceInstance(stackstate.NewResourceInstanceBuilder(). | |
| SetAddr(mustAbsResourceInstanceObject("component.create.testing_resource.resource")). | |
| SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "resource", | |
| "value": nil, | |
| }), | |
| Status: states.ObjectReady, | |
| }). | |
| SetProviderAddr(mustDefaultRootProvider("testing"))). | |
| Build(), | |
| store: stacks_testing_provider.NewResourceStoreBuilder().AddResource("credentials", cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("credentials"), | |
| // we have the wrong value in state, so this correct value must | |
| // be loaded for this test to work. | |
| "value": cty.StringVal("authn"), | |
| })).Build(), | |
| mutators: []func(store *stacks_testing_provider.ResourceStore, testContext TestContext) TestContext{ | |
| func(store *stacks_testing_provider.ResourceStore, testContext TestContext) TestContext { | |
| testContext.providers[addrs.NewDefaultProvider("testing")] = func() (providers.Interface, error) { | |
| provider := stacks_testing_provider.NewProviderWithData(t, store) | |
| provider.Authentication = "authn" // So we must reload the data source in order to authenticate. | |
| return provider, nil | |
| } | |
| return testContext | |
| }, | |
| }, | |
| cycles: []TestCycle{ | |
| { | |
| planMode: plans.DestroyMode, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.create"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.create"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.create.testing_resource.resource"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.load"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.load"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.load.data.testing_data_source.credentials"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "empty-destroy-with-data-source": { | |
| path: path.Join("with-data-source", "dependent"), | |
| cycles: []TestCycle{ | |
| { | |
| planMode: plans.DestroyMode, | |
| planInputs: map[string]cty.Value{ | |
| "id": cty.StringVal("foo"), | |
| }, | |
| // deliberately empty, as we expect no changes from an | |
| // empty state. | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.data"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.data"), | |
| }, | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.self"), | |
| }, | |
| &stackstate.AppliedChangeInputVariable{ | |
| Addr: mustStackInputVariable("id"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "destroy after manual removal": { | |
| path: "removed-offline", | |
| state: stackstate.NewStateBuilder(). | |
| AddComponentInstance(stackstate.NewComponentInstanceBuilder(mustAbsComponentInstance("component.parent")). | |
| AddDependent(mustAbsComponent("component.child")). | |
| AddOutputValue("value", cty.StringVal("hello"))). | |
| AddResourceInstance(stackstate.NewResourceInstanceBuilder(). | |
| SetAddr(mustAbsResourceInstanceObject("component.parent.testing_resource.resource")). | |
| SetProviderAddr(mustDefaultRootProvider("testing")). | |
| SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "parent", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| })). | |
| AddComponentInstance(stackstate.NewComponentInstanceBuilder(mustAbsComponentInstance("component.child")). | |
| AddDependency(mustAbsComponent("component.parent")). | |
| AddInputVariable("value", cty.StringVal("hello"))). | |
| AddResourceInstance(stackstate.NewResourceInstanceBuilder(). | |
| SetAddr(mustAbsResourceInstanceObject("component.child.testing_resource.resource")). | |
| SetProviderAddr(mustDefaultRootProvider("testing")). | |
| SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "child", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| })). | |
| Build(), | |
| store: stacks_testing_provider.NewResourceStoreBuilder(). | |
| AddResource("child", cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("child"), | |
| "value": cty.StringVal("hello"), | |
| })).Build(), | |
| cycles: []TestCycle{ | |
| { | |
| planMode: plans.DestroyMode, | |
| wantPlannedChanges: []stackplan.PlannedChange{ | |
| &stackplan.PlannedChangeApplyable{ | |
| Applyable: true, | |
| }, | |
| &stackplan.PlannedChangeComponentInstance{ | |
| Addr: mustAbsComponentInstance("component.child"), | |
| Action: plans.Delete, | |
| Mode: plans.DestroyMode, | |
| PlanComplete: true, | |
| PlanApplyable: true, | |
| RequiredComponents: collections.NewSet(mustAbsComponent("component.parent")), | |
| PlannedInputValues: map[string]plans.DynamicValue{ | |
| "value": mustPlanDynamicValueDynamicType(cty.UnknownVal(cty.String)), | |
| }, | |
| PlannedInputValueMarks: map[string][]cty.PathValueMarks{ | |
| "value": nil, | |
| }, | |
| PlannedOutputValues: make(map[string]cty.Value), | |
| PlannedCheckResults: &states.CheckResults{}, | |
| PlanTimestamp: fakePlanTimestamp, | |
| }, | |
| &stackplan.PlannedChangeResourceInstancePlanned{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.child.testing_resource.resource"), | |
| ChangeSrc: &plans.ResourceInstanceChangeSrc{ | |
| Addr: mustAbsResourceInstance("testing_resource.resource"), | |
| PrevRunAddr: mustAbsResourceInstance("testing_resource.resource"), | |
| ProviderAddr: mustDefaultRootProvider("testing"), | |
| ChangeSrc: plans.ChangeSrc{ | |
| Action: plans.Delete, | |
| Before: mustPlanDynamicValue(cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("child"), | |
| "value": cty.StringVal("hello"), | |
| })), | |
| After: mustPlanDynamicValue(cty.NullVal(cty.Object(map[string]cty.Type{ | |
| "id": cty.String, | |
| "value": cty.String, | |
| }))), | |
| }, | |
| }, | |
| PriorStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "child", | |
| "value": "hello", | |
| }), | |
| Status: states.ObjectReady, | |
| Dependencies: make([]addrs.ConfigResource, 0), | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackplan.PlannedChangeComponentInstance{ | |
| Addr: mustAbsComponentInstance("component.parent"), | |
| Action: plans.Delete, | |
| Mode: plans.DestroyMode, | |
| PlanComplete: true, | |
| PlanApplyable: false, | |
| PlannedInputValues: make(map[string]plans.DynamicValue), | |
| PlannedOutputValues: map[string]cty.Value{ | |
| "value": cty.UnknownVal(cty.String), | |
| }, | |
| PlannedCheckResults: &states.CheckResults{}, | |
| PlanTimestamp: fakePlanTimestamp, | |
| }, | |
| &stackplan.PlannedChangeResourceInstancePlanned{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.resource"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| &stackplan.PlannedChangeHeader{ | |
| TerraformVersion: version.SemVer, | |
| }, | |
| &stackplan.PlannedChangePlannedTimestamp{ | |
| PlannedTimestamp: fakePlanTimestamp, | |
| }, | |
| }, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.child"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.child"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.child.testing_resource.resource"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.parent"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.parent"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.resource"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "partial destroy recovery": { | |
| path: "component-chain", | |
| description: "this test simulates a partial destroy recovery", | |
| state: stackstate.NewStateBuilder(). | |
| // we only have data for the first component, indicating that | |
| // the second and third components were destroyed but not the | |
| // first one for some reason | |
| AddComponentInstance(stackstate.NewComponentInstanceBuilder(mustAbsComponentInstance("component.one")). | |
| AddDependent(mustAbsComponent("component.two")). | |
| AddInputVariable("id", cty.StringVal("one")). | |
| AddInputVariable("value", cty.StringVal("foo")). | |
| AddOutputValue("value", cty.StringVal("foo"))). | |
| AddResourceInstance(stackstate.NewResourceInstanceBuilder(). | |
| SetAddr(mustAbsResourceInstanceObject("component.one.testing_resource.data")). | |
| SetProviderAddr(mustDefaultRootProvider("testing")). | |
| SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "one", | |
| "value": "foo", | |
| }), | |
| Status: states.ObjectReady, | |
| })). | |
| AddInput("value", cty.StringVal("foo")). | |
| AddOutput("value", cty.StringVal("foo")). | |
| Build(), | |
| store: stacks_testing_provider.NewResourceStoreBuilder(). | |
| AddResource("one", cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("one"), | |
| "value": cty.StringVal("foo"), | |
| })). | |
| Build(), | |
| cycles: []TestCycle{ | |
| { | |
| planMode: plans.DestroyMode, | |
| planInputs: map[string]cty.Value{ | |
| "value": cty.StringVal("foo"), | |
| }, | |
| wantPlannedChanges: []stackplan.PlannedChange{ | |
| &stackplan.PlannedChangeApplyable{ | |
| Applyable: true, | |
| }, | |
| &stackplan.PlannedChangeComponentInstance{ | |
| Addr: mustAbsComponentInstance("component.one"), | |
| Action: plans.Delete, | |
| Mode: plans.DestroyMode, | |
| PlanComplete: true, | |
| PlanApplyable: true, | |
| PlannedInputValues: map[string]plans.DynamicValue{ | |
| "id": mustPlanDynamicValueDynamicType(cty.StringVal("one")), | |
| "value": mustPlanDynamicValueDynamicType(cty.StringVal("foo")), | |
| }, | |
| PlannedInputValueMarks: map[string][]cty.PathValueMarks{ | |
| "id": nil, | |
| "value": nil, | |
| }, | |
| PlannedOutputValues: map[string]cty.Value{ | |
| "value": cty.StringVal("foo"), | |
| }, | |
| PlannedCheckResults: &states.CheckResults{}, | |
| PlanTimestamp: fakePlanTimestamp, | |
| }, | |
| &stackplan.PlannedChangeResourceInstancePlanned{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("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("foo"), | |
| })), | |
| After: mustPlanDynamicValue(cty.NullVal(cty.Object(map[string]cty.Type{ | |
| "id": cty.String, | |
| "value": cty.String, | |
| }))), | |
| }, | |
| }, | |
| PriorStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "one", | |
| "value": "foo", | |
| }), | |
| Status: states.ObjectReady, | |
| Dependencies: make([]addrs.ConfigResource, 0), | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackplan.PlannedChangeComponentInstance{ | |
| Addr: mustAbsComponentInstance("component.three"), | |
| Action: plans.Delete, | |
| Mode: plans.DestroyMode, | |
| PlanComplete: true, | |
| PlanApplyable: true, | |
| RequiredComponents: collections.NewSet(mustAbsComponent("component.two")), | |
| PlannedOutputValues: map[string]cty.Value{ | |
| "value": cty.StringVal("foo"), | |
| }, | |
| PlanTimestamp: fakePlanTimestamp, | |
| }, | |
| &stackplan.PlannedChangeComponentInstance{ | |
| Addr: mustAbsComponentInstance("component.two"), | |
| Action: plans.Delete, | |
| Mode: plans.DestroyMode, | |
| PlanComplete: true, | |
| PlanApplyable: true, | |
| RequiredComponents: collections.NewSet(mustAbsComponent("component.one")), | |
| PlannedOutputValues: map[string]cty.Value{ | |
| "value": cty.StringVal("foo"), | |
| }, | |
| PlanTimestamp: fakePlanTimestamp, | |
| }, | |
| &stackplan.PlannedChangeHeader{ | |
| TerraformVersion: version.SemVer, | |
| }, | |
| &stackplan.PlannedChangeOutputValue{ | |
| Addr: mustStackOutputValue("value"), | |
| Action: plans.Delete, | |
| Before: cty.StringVal("foo"), | |
| After: cty.NullVal(cty.String), | |
| }, | |
| &stackplan.PlannedChangePlannedTimestamp{ | |
| PlannedTimestamp: fakePlanTimestamp, | |
| }, | |
| &stackplan.PlannedChangeRootInputValue{ | |
| Addr: mustStackInputVariable("value"), | |
| Action: plans.NoOp, | |
| Before: cty.StringVal("foo"), | |
| After: cty.StringVal("foo"), | |
| DeleteOnApply: true, | |
| }, | |
| }, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.one"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.one"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.one.testing_resource.data"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.three"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.three"), | |
| }, | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.two"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.two"), | |
| }, | |
| &stackstate.AppliedChangeOutputValue{ | |
| Addr: mustStackOutputValue("value"), | |
| }, | |
| &stackstate.AppliedChangeInputVariable{ | |
| Addr: mustStackInputVariable("value"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "destroy-partial-state-with-module": { | |
| path: "with-module", | |
| state: stackstate.NewStateBuilder(). | |
| AddComponentInstance(stackstate.NewComponentInstanceBuilder(mustAbsComponentInstance("component.self")). | |
| AddInputVariable("id", cty.StringVal("self")). | |
| AddInputVariable("input", cty.StringVal("self"))). | |
| AddResourceInstance(stackstate.NewResourceInstanceBuilder(). | |
| SetAddr(mustAbsResourceInstanceObject("component.self.testing_resource.outside")). | |
| SetProviderAddr(mustDefaultRootProvider("testing")). | |
| SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "self", | |
| "value": "self", | |
| }), | |
| Status: states.ObjectReady, | |
| })). | |
| Build(), | |
| store: stacks_testing_provider.NewResourceStoreBuilder(). | |
| AddResource("self", cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("self"), | |
| "value": cty.StringVal("self"), | |
| })). | |
| Build(), | |
| cycles: []TestCycle{ | |
| { | |
| planMode: plans.DestroyMode, | |
| planInputs: map[string]cty.Value{ | |
| "id": cty.StringVal("self"), | |
| "input": cty.StringVal("self"), | |
| }, | |
| wantPlannedChanges: []stackplan.PlannedChange{ | |
| &stackplan.PlannedChangeApplyable{ | |
| Applyable: true, | |
| }, | |
| &stackplan.PlannedChangeComponentInstance{ | |
| Addr: mustAbsComponentInstance("component.self"), | |
| Action: plans.Delete, | |
| Mode: plans.DestroyMode, | |
| PlanApplyable: true, | |
| PlanComplete: true, | |
| PlannedInputValues: map[string]plans.DynamicValue{ | |
| "create": mustPlanDynamicValueDynamicType(cty.True), | |
| "id": mustPlanDynamicValueDynamicType(cty.StringVal("self")), | |
| "input": mustPlanDynamicValueDynamicType(cty.StringVal("self")), | |
| }, | |
| PlannedInputValueMarks: map[string][]cty.PathValueMarks{ | |
| "create": nil, | |
| "id": nil, | |
| "input": nil, | |
| }, | |
| PlannedOutputValues: make(map[string]cty.Value), | |
| PlannedCheckResults: new(states.CheckResults), | |
| PlanTimestamp: fakePlanTimestamp, | |
| }, | |
| &stackplan.PlannedChangeResourceInstancePlanned{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.outside"), | |
| ChangeSrc: &plans.ResourceInstanceChangeSrc{ | |
| Addr: mustAbsResourceInstance("testing_resource.outside"), | |
| PrevRunAddr: mustAbsResourceInstance("testing_resource.outside"), | |
| ProviderAddr: mustDefaultRootProvider("testing"), | |
| ChangeSrc: plans.ChangeSrc{ | |
| Action: plans.Delete, | |
| Before: mustPlanDynamicValue(cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("self"), | |
| "value": cty.StringVal("self"), | |
| })), | |
| After: mustPlanDynamicValue(cty.NullVal(cty.Object(map[string]cty.Type{ | |
| "id": cty.String, | |
| "value": cty.String, | |
| }))), | |
| }, | |
| }, | |
| PriorStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "self", | |
| "value": "self", | |
| }), | |
| 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{ | |
| Addr: mustStackInputVariable("id"), | |
| Action: plans.Create, | |
| Before: cty.NullVal(cty.DynamicPseudoType), | |
| After: cty.StringVal("self"), | |
| DeleteOnApply: true, | |
| }, | |
| &stackplan.PlannedChangeRootInputValue{ | |
| Addr: mustStackInputVariable("input"), | |
| Action: plans.Create, | |
| Before: cty.NullVal(cty.DynamicPseudoType), | |
| After: cty.StringVal("self"), | |
| DeleteOnApply: true, | |
| }, | |
| }, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.self"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.outside"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| &stackstate.AppliedChangeInputVariable{ | |
| Addr: mustStackInputVariable("id"), | |
| }, | |
| &stackstate.AppliedChangeInputVariable{ | |
| Addr: mustStackInputVariable("input"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "destroy-partial-state": { | |
| path: "destroy-partial-state", | |
| state: stackstate.NewStateBuilder(). | |
| AddComponentInstance(stackstate.NewComponentInstanceBuilder(mustAbsComponentInstance("component.parent")). | |
| AddDependent(mustAbsComponent("component.child"))). | |
| AddResourceInstance(stackstate.NewResourceInstanceBuilder(). | |
| SetAddr(mustAbsResourceInstanceObject("component.parent.testing_resource.primary")). | |
| SetProviderAddr(mustDefaultRootProvider("testing")). | |
| SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "primary", | |
| }), | |
| Status: states.ObjectReady, | |
| })). | |
| AddResourceInstance(stackstate.NewResourceInstanceBuilder(). | |
| SetAddr(mustAbsResourceInstanceObject("component.parent.testing_resource.secondary")). | |
| SetProviderAddr(mustDefaultRootProvider("testing")). | |
| SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "secondary", | |
| "value": "primary", | |
| }), | |
| Status: states.ObjectReady, | |
| })). | |
| Build(), | |
| store: stacks_testing_provider.NewResourceStoreBuilder(). | |
| AddResource("primary", cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("primary"), | |
| "value": cty.NullVal(cty.String), | |
| })). | |
| AddResource("secondary", cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("secondary"), | |
| "value": cty.StringVal("primary"), | |
| })). | |
| Build(), | |
| cycles: []TestCycle{ | |
| { | |
| planMode: plans.DestroyMode, | |
| wantPlannedChanges: []stackplan.PlannedChange{ | |
| &stackplan.PlannedChangeApplyable{ | |
| Applyable: true, | |
| }, | |
| &stackplan.PlannedChangeComponentInstance{ | |
| Addr: mustAbsComponentInstance("component.child"), | |
| Action: plans.Delete, | |
| Mode: plans.DestroyMode, | |
| PlanApplyable: true, | |
| PlanComplete: true, | |
| RequiredComponents: collections.NewSet(mustAbsComponent("component.parent")), | |
| PlannedOutputValues: make(map[string]cty.Value), | |
| PlanTimestamp: fakePlanTimestamp, | |
| }, | |
| &stackplan.PlannedChangeComponentInstance{ | |
| Addr: mustAbsComponentInstance("component.parent"), | |
| Action: plans.Delete, | |
| Mode: plans.DestroyMode, | |
| PlanApplyable: true, | |
| PlanComplete: true, | |
| PlannedInputValues: make(map[string]plans.DynamicValue), | |
| PlannedOutputValues: map[string]cty.Value{ | |
| "deleted_id": cty.UnknownVal(cty.String), | |
| }, | |
| PlannedCheckResults: &states.CheckResults{}, | |
| PlanTimestamp: fakePlanTimestamp, | |
| }, | |
| &stackplan.PlannedChangeResourceInstancePlanned{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.primary"), | |
| ChangeSrc: &plans.ResourceInstanceChangeSrc{ | |
| Addr: mustAbsResourceInstance("testing_resource.primary"), | |
| PrevRunAddr: mustAbsResourceInstance("testing_resource.primary"), | |
| ProviderAddr: mustDefaultRootProvider("testing"), | |
| ChangeSrc: plans.ChangeSrc{ | |
| Action: plans.Delete, | |
| Before: mustPlanDynamicValue(cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("primary"), | |
| "value": cty.NullVal(cty.String), | |
| })), | |
| After: mustPlanDynamicValue(cty.NullVal(cty.Object(map[string]cty.Type{ | |
| "id": cty.String, | |
| "value": cty.String, | |
| }))), | |
| }, | |
| }, | |
| PriorStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "primary", | |
| "value": nil, | |
| }), | |
| Status: states.ObjectReady, | |
| Dependencies: make([]addrs.ConfigResource, 0), | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackplan.PlannedChangeResourceInstancePlanned{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.secondary"), | |
| ChangeSrc: &plans.ResourceInstanceChangeSrc{ | |
| Addr: mustAbsResourceInstance("testing_resource.secondary"), | |
| PrevRunAddr: mustAbsResourceInstance("testing_resource.secondary"), | |
| ProviderAddr: mustDefaultRootProvider("testing"), | |
| ChangeSrc: plans.ChangeSrc{ | |
| Action: plans.Delete, | |
| Before: mustPlanDynamicValue(cty.ObjectVal(map[string]cty.Value{ | |
| "id": cty.StringVal("secondary"), | |
| "value": cty.StringVal("primary"), | |
| })), | |
| After: mustPlanDynamicValue(cty.NullVal(cty.Object(map[string]cty.Type{ | |
| "id": cty.String, | |
| "value": cty.String, | |
| }))), | |
| }, | |
| }, | |
| PriorStateSrc: &states.ResourceInstanceObjectSrc{ | |
| AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ | |
| "id": "secondary", | |
| "value": "primary", | |
| }), | |
| Status: states.ObjectReady, | |
| Dependencies: []addrs.ConfigResource{ | |
| mustAbsResourceInstance("testing_resource.primary").ConfigResource(), | |
| }, | |
| }, | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| Schema: stacks_testing_provider.TestingResourceSchema, | |
| }, | |
| &stackplan.PlannedChangeHeader{ | |
| TerraformVersion: version.SemVer, | |
| }, | |
| &stackplan.PlannedChangePlannedTimestamp{ | |
| PlannedTimestamp: fakePlanTimestamp, | |
| }, | |
| }, | |
| wantAppliedChanges: []stackstate.AppliedChange{ | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.child"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.child"), | |
| }, | |
| &stackstate.AppliedChangeComponentInstanceRemoved{ | |
| ComponentAddr: mustAbsComponent("component.parent"), | |
| ComponentInstanceAddr: mustAbsComponentInstance("component.parent"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.primary"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| &stackstate.AppliedChangeResourceInstanceObject{ | |
| ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.parent.testing_resource.secondary"), | |
| ProviderConfigAddr: mustDefaultRootProvider("testing"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "destroy-with-follow-up": { | |
| path: filepath.Join("with-single-input", "valid"), | |
| cycles: []TestCycle{ | |
| { | |
| planMode: plans.NormalMode, // create | |
| planInputs: map[string]cty.Value{ | |
| "id": cty.StringVal("self"), | |
| "input": cty.StringVal("self"), | |
| }, | |
| }, | |
| { | |
| planMode: plans.DestroyMode, // destroy | |
| planInputs: map[string]cty.Value{ | |
| "id": cty.StringVal("self"), | |
| "input": cty.StringVal("self"), | |
| }, | |
| wantPlannedHooks: &ExpectedHooks{ | |
| ComponentExpanded: []*hooks.ComponentInstances{ | |
| { | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| InstanceAddrs: []stackaddrs.AbsComponentInstance{mustAbsComponentInstance("component.self")}, | |
| }, | |
| }, | |
| PendingComponentInstancePlan: collections.NewSet[stackaddrs.AbsComponentInstance]( | |
| mustAbsComponentInstance("component.self"), | |
| ), | |
| BeginComponentInstancePlan: collections.NewSet[stackaddrs.AbsComponentInstance]( | |
| mustAbsComponentInstance("component.self"), | |
| ), | |
| EndComponentInstancePlan: collections.NewSet[stackaddrs.AbsComponentInstance]( | |
| mustAbsComponentInstance("component.self"), | |
| ), | |
| ReportResourceInstanceStatus: []*hooks.ResourceInstanceStatusHookData{ | |
| { | |
| Addr: mustAbsResourceInstanceObject("component.self.testing_resource.data"), | |
| ProviderAddr: mustDefaultRootProvider("testing").Provider, | |
| Status: hooks.ResourceInstancePlanning, | |
| }, | |
| { | |
| Addr: mustAbsResourceInstanceObject("component.self.testing_resource.data"), | |
| ProviderAddr: mustDefaultRootProvider("testing").Provider, | |
| Status: hooks.ResourceInstancePlanned, | |
| }, | |
| }, | |
| ReportResourceInstancePlanned: []*hooks.ResourceInstanceChange{ | |
| { | |
| Addr: mustAbsResourceInstanceObject("component.self.testing_resource.data"), | |
| Change: &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("self"), | |
| "value": cty.StringVal("self"), | |
| })), | |
| After: mustPlanDynamicValue(cty.NullVal(cty.Object(map[string]cty.Type{ | |
| "id": cty.String, | |
| "value": cty.String, | |
| }))), | |
| }, | |
| }, | |
| }, | |
| }, | |
| ReportComponentInstancePlanned: []*hooks.ComponentInstanceChange{ | |
| { | |
| Addr: mustAbsComponentInstance("component.self"), | |
| Remove: 1, | |
| }, | |
| }, | |
| }, | |
| wantAppliedHooks: &ExpectedHooks{ | |
| ComponentExpanded: []*hooks.ComponentInstances{ | |
| { | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| InstanceAddrs: []stackaddrs.AbsComponentInstance{mustAbsComponentInstance("component.self")}, | |
| }, | |
| }, | |
| PendingComponentInstanceApply: collections.NewSet[stackaddrs.AbsComponentInstance]( | |
| mustAbsComponentInstance("component.self"), | |
| ), | |
| BeginComponentInstanceApply: collections.NewSet[stackaddrs.AbsComponentInstance]( | |
| mustAbsComponentInstance("component.self"), | |
| ), | |
| EndComponentInstanceApply: collections.NewSet[stackaddrs.AbsComponentInstance]( | |
| mustAbsComponentInstance("component.self"), | |
| ), | |
| ReportResourceInstanceStatus: []*hooks.ResourceInstanceStatusHookData{ | |
| { | |
| Addr: mustAbsResourceInstanceObject("component.self.testing_resource.data"), | |
| ProviderAddr: mustDefaultRootProvider("testing").Provider, | |
| Status: hooks.ResourceInstanceApplying, | |
| }, | |
| { | |
| Addr: mustAbsResourceInstanceObject("component.self.testing_resource.data"), | |
| ProviderAddr: mustDefaultRootProvider("testing").Provider, | |
| Status: hooks.ResourceInstanceApplied, | |
| }, | |
| }, | |
| ReportComponentInstanceApplied: []*hooks.ComponentInstanceChange{ | |
| { | |
| Addr: mustAbsComponentInstance("component.self"), | |
| Remove: 1, | |
| }, | |
| }, | |
| }, | |
| }, | |
| { | |
| planMode: plans.DestroyMode, // should be empty destroy | |
| planInputs: map[string]cty.Value{ | |
| "id": cty.StringVal("self"), | |
| "input": cty.StringVal("self"), | |
| }, | |
| wantPlannedHooks: &ExpectedHooks{ | |
| ComponentExpanded: []*hooks.ComponentInstances{ | |
| { | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| InstanceAddrs: []stackaddrs.AbsComponentInstance{mustAbsComponentInstance("component.self")}, | |
| }, | |
| }, | |
| PendingComponentInstancePlan: collections.NewSet[stackaddrs.AbsComponentInstance]( | |
| mustAbsComponentInstance("component.self"), | |
| ), | |
| BeginComponentInstancePlan: collections.NewSet[stackaddrs.AbsComponentInstance]( | |
| mustAbsComponentInstance("component.self"), | |
| ), | |
| EndComponentInstancePlan: collections.NewSet[stackaddrs.AbsComponentInstance]( | |
| mustAbsComponentInstance("component.self"), | |
| ), | |
| ReportComponentInstancePlanned: []*hooks.ComponentInstanceChange{{ | |
| Addr: mustAbsComponentInstance("component.self"), | |
| }}, | |
| }, | |
| wantAppliedHooks: &ExpectedHooks{ | |
| ComponentExpanded: []*hooks.ComponentInstances{ | |
| { | |
| ComponentAddr: mustAbsComponent("component.self"), | |
| InstanceAddrs: []stackaddrs.AbsComponentInstance{mustAbsComponentInstance("component.self")}, | |
| }, | |
| }, | |
| PendingComponentInstanceApply: collections.NewSet[stackaddrs.AbsComponentInstance]( | |
| mustAbsComponentInstance("component.self"), | |
| ), | |
| BeginComponentInstanceApply: collections.NewSet[stackaddrs.AbsComponentInstance]( | |
| mustAbsComponentInstance("component.self"), | |
| ), | |
| EndComponentInstanceApply: collections.NewSet[stackaddrs.AbsComponentInstance]( | |
| mustAbsComponentInstance("component.self"), | |
| ), | |
| ReportComponentInstanceApplied: []*hooks.ComponentInstanceChange{{ | |
| Addr: mustAbsComponentInstance("component.self"), | |
| }}, | |
| }, | |
| }, | |
| }, | |
| }, | |
| } | |
| 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.1.0"), | |
| providerreqs.MustParseVersionConstraints("0.1.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, | |
| } | |
| state := tc.state | |
| for ix, cycle := range tc.cycles { | |
| if tc.mutators != nil { | |
| testContext = tc.mutators[ix](store, testContext) | |
| } | |
| t.Run(strconv.FormatInt(int64(ix), 10), func(t *testing.T) { | |
| var plan *stackplan.Plan | |
| t.Run("plan", func(t *testing.T) { | |
| plan = testContext.Plan(t, ctx, state, cycle) | |
| }) | |
| t.Run("apply", func(t *testing.T) { | |
| state = testContext.Apply(t, ctx, plan, cycle) | |
| }) | |
| }) | |
| } | |
| }) | |
| } | |
| } |