stacks: handle input and output state during delete plans (#35726)

pull/35734/head
Liam Cervante 2 years ago committed by GitHub
parent 3b30caa42b
commit 598648b66f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -74,6 +74,12 @@ type PlannedChangeRootInputValue struct {
// plan and apply, but a value set during planning can have a different
// value during apply.
RequiredOnApply bool
// DeleteOnApply is true if this variable should be removed from the state
// on apply even if it was not actively removed from the configuration in
// a delete action. This is typically the case during a destroy only plan
// in which we want to update the state to remove everything.
DeleteOnApply bool
}
var _ PlannedChange = (*PlannedChangeRootInputValue)(nil)
@ -85,14 +91,18 @@ func (pc *PlannedChangeRootInputValue) PlannedChangeProto() (*stacks.PlannedChan
return nil, err
}
var raw anypb.Any
if pc.Action == plans.Delete {
var raws []*anypb.Any
if pc.Action == plans.Delete || pc.DeleteOnApply {
var raw anypb.Any
if err := anypb.MarshalFrom(&raw, &tfstackdata1.DeletedRootInputVariable{
Name: pc.Addr.Name,
}, proto.MarshalOptions{}); err != nil {
return nil, fmt.Errorf("failed to encode raw state for %s: %w", pc.Addr, err)
}
} else {
raws = append(raws, &raw)
}
if pc.Action != plans.Delete {
var ppdv *tfstackdata1.DynamicValue
if pc.After != cty.NilVal {
ppdv, err = tfstackdata1.DynamicValueToTFStackData1(pc.After, cty.DynamicPseudoType)
@ -100,6 +110,7 @@ func (pc *PlannedChangeRootInputValue) PlannedChangeProto() (*stacks.PlannedChan
return nil, fmt.Errorf("failed to encode raw state for %s: %w", pc.Addr, err)
}
}
var raw anypb.Any
if err := anypb.MarshalFrom(&raw, &tfstackdata1.PlanRootInputValue{
Name: pc.Addr.Name,
Value: ppdv,
@ -107,6 +118,7 @@ func (pc *PlannedChangeRootInputValue) PlannedChangeProto() (*stacks.PlannedChan
}, proto.MarshalOptions{}); err != nil {
return nil, err
}
raws = append(raws, &raw)
}
var before, after *stacks.DynamicValue
@ -124,7 +136,7 @@ func (pc *PlannedChangeRootInputValue) PlannedChangeProto() (*stacks.PlannedChan
}
return &stacks.PlannedChange{
Raw: []*anypb.Any{&raw},
Raw: raws,
Descriptions: []*stacks.PlannedChange_ChangeDescription{
{
Description: &stacks.PlannedChange_ChangeDescription_InputVariablePlanned{

@ -42,6 +42,55 @@ func TestApplyDestroy(t *testing.T) {
store *stacks_testing_provider.ResourceStore
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"),
Removed: true, // 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",
@ -86,14 +135,12 @@ func TestApplyDestroy(t *testing.T) {
Schema: nil,
},
&stackstate.AppliedChangeInputVariable{
Addr: mustStackInputVariable("id"),
Value: cty.NullVal(cty.String),
// Removed: true, TODO: Enable this in a follow up PR.
Addr: mustStackInputVariable("id"),
Removed: true,
},
&stackstate.AppliedChangeInputVariable{
Addr: mustStackInputVariable("input"),
Value: cty.StringVal("hello"),
// Removed: true, TODO: Enable this in a follow up PR.
Addr: mustStackInputVariable("input"),
Removed: true,
},
},
},
@ -159,14 +206,12 @@ func TestApplyDestroy(t *testing.T) {
NewStateSrc: nil,
},
&stackstate.AppliedChangeInputVariable{
Addr: mustStackInputVariable("id"),
Value: cty.StringVal("foo"),
// Removed: true, TODO: Enable this in a follow up PR.
Addr: mustStackInputVariable("id"),
Removed: true,
},
&stackstate.AppliedChangeInputVariable{
Addr: mustStackInputVariable("resource"),
Value: cty.StringVal("bar"),
// Removed: true, TODO: Enable this in a follow up PR.
Addr: mustStackInputVariable("resource"),
Removed: true,
},
},
},
@ -249,12 +294,10 @@ func TestApplyDestroy(t *testing.T) {
&stackstate.AppliedChangeInputVariable{
Addr: mustStackInputVariable("id"),
Value: cty.StringVal("foo"),
// Removed: true, TODO: Enable this in a follow up PR.
},
&stackstate.AppliedChangeInputVariable{
Addr: mustStackInputVariable("resource"),
Value: cty.StringVal("bar"),
// Removed: true, TODO: Enable this in a follow up PR.
},
},
},
@ -287,14 +330,12 @@ func TestApplyDestroy(t *testing.T) {
NewStateSrc: nil, // deleted
},
&stackstate.AppliedChangeInputVariable{
Addr: mustStackInputVariable("id"),
Value: cty.StringVal("foo"),
// Removed: true, TODO: Enable this in a follow up PR.
Addr: mustStackInputVariable("id"),
Removed: true,
},
&stackstate.AppliedChangeInputVariable{
Addr: mustStackInputVariable("resource"),
Value: cty.StringVal("bar"),
// Removed: true, TODO: Enable this in a follow up PR.
Addr: mustStackInputVariable("resource"),
Removed: true,
},
},
},
@ -452,14 +493,12 @@ func TestApplyDestroy(t *testing.T) {
Schema: stacks_testing_provider.FailedResourceSchema,
},
&stackstate.AppliedChangeInputVariable{
Addr: mustStackInputVariable("fail_apply"),
Value: cty.False,
// Removed: true, TODO: Enable this in a follow up PR.
Addr: mustStackInputVariable("fail_apply"),
Removed: true,
},
&stackstate.AppliedChangeInputVariable{
Addr: mustStackInputVariable("fail_plan"),
Value: cty.False,
// Removed: true, TODO: Enable this in a follow up PR.
Addr: mustStackInputVariable("fail_plan"),
Removed: true,
},
},
wantAppliedDiags: initDiags(func(diags tfdiags.Diagnostics) tfdiags.Diagnostics {

@ -243,6 +243,8 @@ func (v *InputVariable) PlanChanges(ctx context.Context) ([]stackplan.PlannedCha
return nil, diags
}
destroy := v.main.PlanningOpts().PlanningMode == plans.DestroyMode
before, beforeEphemeral := v.main.PlanPrevState().RootInputVariable(v.Addr().Item)
decl := v.Declaration(ctx)
@ -290,6 +292,7 @@ func (v *InputVariable) PlanChanges(ctx context.Context) ([]stackplan.PlannedCha
Before: before,
After: after,
RequiredOnApply: requiredOnApply,
DeleteOnApply: destroy,
},
}, diags
}
@ -331,6 +334,15 @@ func (v *InputVariable) CheckApply(ctx context.Context) ([]stackstate.AppliedCha
return nil, diags
}
if v.main.PlanBeingApplied().DeletedInputVariables.Has(v.Addr().Item) {
// If the plan being applied has this variable as being deleted, then
// we won't handle it here. This is usually the case during a destroy
// only plan in which we wanted to both capture the value for an input
// as we still need it, while also noting that everything is being
// destroyed.
return nil, diags
}
decl := v.Declaration(ctx)
value := v.Value(ctx, ApplyPhase)
if decl.Ephemeral {

@ -761,33 +761,59 @@ func (s *Stack) PlanChanges(ctx context.Context) ([]stackplan.PlannedChange, tfd
// correctly implemented.
panic(fmt.Sprintf("invalid result from Stack.ResultValue: %#v", afterVal))
}
beforeVal := s.main.PlanPrevState().RootOutputValues()
var changes []stackplan.PlannedChange
for it := afterVal.ElementIterator(); it.Next(); {
k, after := it.Element()
addr := stackaddrs.OutputValue{Name: k.AsString()}
before := cty.NullVal(cty.DynamicPseudoType)
action := plans.Create
if actualBefore, exists := beforeVal[addr]; exists {
before = actualBefore
if result := before.Equals(after); result.IsKnown() && result.True() {
action = plans.NoOp
} else {
action = plans.Update
if s.main.PlanningOpts().PlanningMode == plans.DestroyMode {
// For a destroy plan, we'll actually cheat a little bit and swap out
// the values for null and destroy actions. We do this here because
// for most stacks and outputs we might have components that rely on
// the output being calculated based on the prior state rather than
// returning null. So, we leave the internals to compute the value
// in a helpful way and then just blanket say that all outputs will be
// destroyed during the plan.
for name, attr := range afterVal.Type().AttributeTypes() {
addr := stackaddrs.OutputValue{Name: name}
if before, exists := beforeVal[addr]; exists {
// If the before doesn't exist, then we'll emit nothing for this
// change as it doesn't already exist in state so doesn't need
// to be destroyed.
changes = append(changes, &stackplan.PlannedChangeOutputValue{
Addr: stackaddrs.OutputValue{Name: name},
Action: plans.Delete,
Before: before,
// We can set the right type here, as do have the
// configuration.
After: cty.NullVal(attr),
})
}
}
} else {
for it := afterVal.ElementIterator(); it.Next(); {
k, after := it.Element()
addr := stackaddrs.OutputValue{Name: k.AsString()}
before := cty.NullVal(cty.DynamicPseudoType)
action := plans.Create
if actualBefore, exists := beforeVal[addr]; exists {
before = actualBefore
if result := before.Equals(after); result.IsKnown() && result.True() {
action = plans.NoOp
} else {
action = plans.Update
}
}
changes = append(changes, &stackplan.PlannedChangeOutputValue{
Addr: addr,
Action: action,
Before: before,
After: after,
})
changes = append(changes, &stackplan.PlannedChangeOutputValue{
Addr: addr,
Action: action,
Before: before,
After: after,
})
}
}
for addr, before := range beforeVal {
@ -843,16 +869,27 @@ func (s *Stack) CheckApply(ctx context.Context) ([]stackstate.AppliedChange, tfd
panic(fmt.Sprintf("invalid result from Stack.ResultValue: %#v", resultVal))
}
deletedOutputValues := s.main.PlanBeingApplied().DeletedOutputValues
var changes []stackstate.AppliedChange
for it := resultVal.ElementIterator(); it.Next(); {
k, v := it.Element()
addr := stackaddrs.OutputValue{Name: k.AsString()}
if deletedOutputValues.Has(addr) {
// Then we are deleting this output value even though it is in the
// configuration for some reason (probably because this is a
// delete plan and we're deleting everything). So, we won't process
// it here and only below.
continue
}
changes = append(changes, &stackstate.AppliedChangeOutputValue{
Addr: stackaddrs.OutputValue{Name: k.AsString()},
Addr: addr,
Value: v,
})
}
for _, value := range s.main.PlanBeingApplied().DeletedOutputValues.Elems() {
for _, value := range deletedOutputValues.Elems() {
// elements that are being deleted will explicitly not show up in our
// result value
changes = append(changes, &stackstate.AppliedChangeOutputValue{

Loading…
Cancel
Save