| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: BUSL-1.1 |
| |
| package stackruntime |
| |
| import ( |
| "context" |
| "path" |
| "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" |
| 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"), |
| }, |
| }, |
| }, |
| }, |
| }, |
| } |
| 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, |
| } |
| |
| 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) |
| }) |
| }) |
| } |
| |
| }) |
| } |
| } |