mirror of
https://github.com/hashicorp/terraform.git
synced 2026-05-28 04:03:27 -04:00
stacks: pre-destroy refresh should use a normal plan (#36696)
* stacks: pre-destroy refresh should use a normal plan * format
This commit is contained in:
parent
c16d466773
commit
9bbe34daa4
7 changed files with 248 additions and 34 deletions
|
|
@ -1121,7 +1121,7 @@ func TestApplyDestroy(t *testing.T) {
|
|||
PlanApplyable: false,
|
||||
PlannedInputValues: make(map[string]plans.DynamicValue),
|
||||
PlannedOutputValues: map[string]cty.Value{
|
||||
"value": cty.DynamicVal,
|
||||
"value": cty.UnknownVal(cty.String),
|
||||
},
|
||||
PlannedCheckResults: &states.CheckResults{},
|
||||
PlanTimestamp: fakePlanTimestamp,
|
||||
|
|
@ -1256,7 +1256,7 @@ func TestApplyDestroy(t *testing.T) {
|
|||
PlanApplyable: true,
|
||||
RequiredComponents: collections.NewSet(mustAbsComponent("component.two")),
|
||||
PlannedOutputValues: map[string]cty.Value{
|
||||
"value": cty.DynamicVal,
|
||||
"value": cty.StringVal("foo"),
|
||||
},
|
||||
PlanTimestamp: fakePlanTimestamp,
|
||||
},
|
||||
|
|
@ -1268,7 +1268,7 @@ func TestApplyDestroy(t *testing.T) {
|
|||
PlanApplyable: true,
|
||||
RequiredComponents: collections.NewSet(mustAbsComponent("component.one")),
|
||||
PlannedOutputValues: map[string]cty.Value{
|
||||
"value": cty.DynamicVal,
|
||||
"value": cty.StringVal("foo"),
|
||||
},
|
||||
PlanTimestamp: fakePlanTimestamp,
|
||||
},
|
||||
|
|
@ -1319,6 +1319,129 @@ func TestApplyDestroy(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"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().
|
||||
|
|
@ -1379,7 +1502,7 @@ func TestApplyDestroy(t *testing.T) {
|
|||
PlanComplete: true,
|
||||
PlannedInputValues: make(map[string]plans.DynamicValue),
|
||||
PlannedOutputValues: map[string]cty.Value{
|
||||
"deleted_id": cty.DynamicVal,
|
||||
"deleted_id": cty.UnknownVal(cty.String),
|
||||
},
|
||||
PlannedCheckResults: &states.CheckResults{},
|
||||
PlanTimestamp: fakePlanTimestamp,
|
||||
|
|
|
|||
|
|
@ -249,34 +249,28 @@ func (c *ComponentInstance) CheckModuleTreePlan(ctx context.Context) (*plans.Pla
|
|||
return nil, diags
|
||||
}
|
||||
|
||||
if !refresh.Complete {
|
||||
// If the refresh was deferred, then we'll defer the destroy
|
||||
// plan as well.
|
||||
opts.ExternalDependencyDeferred = true
|
||||
} else {
|
||||
// If we're destroying this instance, then the dependencies
|
||||
// should be reversed. Unfortunately, we can't compute that
|
||||
// easily so instead we'll use the dependents computed at the
|
||||
// last apply operation.
|
||||
Dependents:
|
||||
for depAddr := range c.PlanPrevDependents(ctx).All() {
|
||||
depStack := c.main.Stack(ctx, depAddr.Stack, PlanPhase)
|
||||
if depStack == nil {
|
||||
// something weird has happened, but this means that
|
||||
// whatever thing we're depending on being deleted first
|
||||
// doesn't exist so it's fine.
|
||||
continue
|
||||
}
|
||||
depComponent, depRemoveds := depStack.ApplyableComponents(ctx, depAddr.Item)
|
||||
if depComponent != nil && !depComponent.PlanIsComplete(ctx) {
|
||||
// If we're destroying this instance, then the dependencies
|
||||
// should be reversed. Unfortunately, we can't compute that
|
||||
// easily so instead we'll use the dependents computed at the
|
||||
// last apply operation.
|
||||
Dependents:
|
||||
for depAddr := range c.PlanPrevDependents(ctx).All() {
|
||||
depStack := c.main.Stack(ctx, depAddr.Stack, PlanPhase)
|
||||
if depStack == nil {
|
||||
// something weird has happened, but this means that
|
||||
// whatever thing we're depending on being deleted first
|
||||
// doesn't exist so it's fine.
|
||||
continue
|
||||
}
|
||||
depComponent, depRemoveds := depStack.ApplyableComponents(ctx, depAddr.Item)
|
||||
if depComponent != nil && !depComponent.PlanIsComplete(ctx) {
|
||||
opts.ExternalDependencyDeferred = true
|
||||
break
|
||||
}
|
||||
for _, depRemoved := range depRemoveds {
|
||||
if !depRemoved.PlanIsComplete(ctx) {
|
||||
opts.ExternalDependencyDeferred = true
|
||||
break
|
||||
}
|
||||
for _, depRemoved := range depRemoveds {
|
||||
if !depRemoved.PlanIsComplete(ctx) {
|
||||
opts.ExternalDependencyDeferred = true
|
||||
break Dependents
|
||||
}
|
||||
break Dependents
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ func (r *RefreshInstance) Result(ctx context.Context) map[string]cty.Value {
|
|||
|
||||
func (r *RefreshInstance) Plan(ctx context.Context) (*plans.Plan, tfdiags.Diagnostics) {
|
||||
return doOnceWithDiags(ctx, &r.moduleTreePlan, r, func(ctx context.Context) (*plans.Plan, tfdiags.Diagnostics) {
|
||||
opts, diags := r.component.PlanOpts(ctx, plans.RefreshOnlyMode, false)
|
||||
opts, diags := r.component.PlanOpts(ctx, plans.NormalMode, false)
|
||||
if opts == nil {
|
||||
return nil, diags
|
||||
}
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ func TestPlan(t *testing.T) {
|
|||
Action: plans.Delete,
|
||||
Mode: plans.DestroyMode,
|
||||
PlannedOutputValues: map[string]cty.Value{
|
||||
"id": cty.NullVal(cty.DynamicPseudoType),
|
||||
"id": cty.StringVal("foo"),
|
||||
},
|
||||
PlanTimestamp: fakePlanTimestamp,
|
||||
},
|
||||
|
|
@ -1186,10 +1186,10 @@ func TestPlanWithEphemeralInputVariables(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
req := PlanRequest{
|
||||
Config: cfg,
|
||||
InputValues: map[stackaddrs.InputVariable]stackeval.ExternalInputValue{
|
||||
// Intentionally not set for this subtest.
|
||||
},
|
||||
Config: cfg,
|
||||
ForcePlanTimestamp: &fakePlanTimestamp,
|
||||
}
|
||||
resp := PlanResponse{
|
||||
|
|
|
|||
23
internal/stacks/stackruntime/testdata/mainbundle/test/with-module/module/module.tf
vendored
Normal file
23
internal/stacks/stackruntime/testdata/mainbundle/test/with-module/module/module.tf
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
testing = {
|
||||
source = "hashicorp/testing"
|
||||
version = "0.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "id" {
|
||||
type = string
|
||||
default = null
|
||||
nullable = true # We'll generate an ID if none provided.
|
||||
}
|
||||
|
||||
variable "input" {
|
||||
type = string
|
||||
}
|
||||
|
||||
resource "testing_resource" "data" {
|
||||
id = var.id
|
||||
value = var.input
|
||||
}
|
||||
44
internal/stacks/stackruntime/testdata/mainbundle/test/with-module/with-module.tf
vendored
Normal file
44
internal/stacks/stackruntime/testdata/mainbundle/test/with-module/with-module.tf
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
testing = {
|
||||
source = "hashicorp/testing"
|
||||
version = "0.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "create" {
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "id" {
|
||||
type = string
|
||||
default = null
|
||||
nullable = true # We'll generate an ID if none provided.
|
||||
}
|
||||
|
||||
variable "input" {
|
||||
type = string
|
||||
}
|
||||
|
||||
resource "testing_resource" "resource" {
|
||||
count = var.create ? 1 : 0
|
||||
}
|
||||
|
||||
|
||||
module "module" {
|
||||
source = "./module"
|
||||
|
||||
providers = {
|
||||
testing = testing
|
||||
}
|
||||
|
||||
id = testing_resource.resource[0].id
|
||||
input = var.input
|
||||
}
|
||||
|
||||
resource "testing_resource" "outside" {
|
||||
id = var.id
|
||||
value = var.input
|
||||
}
|
||||
30
internal/stacks/stackruntime/testdata/mainbundle/test/with-module/with-module.tfstack.hcl
vendored
Normal file
30
internal/stacks/stackruntime/testdata/mainbundle/test/with-module/with-module.tfstack.hcl
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
required_providers {
|
||||
testing = {
|
||||
source = "hashicorp/testing"
|
||||
version = "0.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
provider "testing" "default" {}
|
||||
|
||||
variable "input" {
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "id" {
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
component "self" {
|
||||
source = "./"
|
||||
|
||||
providers = {
|
||||
testing = provider.testing.default
|
||||
}
|
||||
|
||||
inputs = {
|
||||
id = var.id
|
||||
input = var.input
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue