Skip to content

Commit

Permalink
Complete implementation of PreventRemoval
Browse files Browse the repository at this point in the history
  • Loading branch information
ben-z committed Nov 5, 2023
1 parent 7221caa commit 1959d25
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 4 deletions.
8 changes: 8 additions & 0 deletions internal/states/instance_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ type ResourceInstanceObject struct {
// destroy operations, we need to record the status to ensure a resource
// removed from the config will still be destroyed in the same manner.
CreateBeforeDestroy bool

// PreventRemoval is a lifecycle option that can be set on a resource
// instance to prevent it from being removed from state. This is useful
// for preventing a stateful resource from being removed unexpectedly,
// regardless of whether it is still present in configuration.
// This can be thought of as a stronger version of prevent_destroy.
PreventRemoval bool
}

// ObjectStatus represents the status of a RemoteObject.
Expand Down Expand Up @@ -133,6 +140,7 @@ func (o *ResourceInstanceObject) Encode(ty cty.Type, schemaVersion uint64) (*Res
Status: o.Status,
Dependencies: dependencies,
CreateBeforeDestroy: o.CreateBeforeDestroy,
PreventRemoval: o.PreventRemoval,
}, nil
}

Expand Down
2 changes: 2 additions & 0 deletions internal/states/instance_object_src.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type ResourceInstanceObjectSrc struct {
Status ObjectStatus
Dependencies []addrs.ConfigResource
CreateBeforeDestroy bool
PreventRemoval bool
}

// Decode unmarshals the raw representation of the object attributes. Pass the
Expand Down Expand Up @@ -100,6 +101,7 @@ func (os *ResourceInstanceObjectSrc) Decode(ty cty.Type) (*ResourceInstanceObjec
Dependencies: os.Dependencies,
Private: os.Private,
CreateBeforeDestroy: os.CreateBeforeDestroy,
PreventRemoval: os.PreventRemoval,
}, nil
}

Expand Down
2 changes: 2 additions & 0 deletions internal/states/state_deepcopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ func (os *ResourceInstanceObjectSrc) DeepCopy() *ResourceInstanceObjectSrc {
AttrSensitivePaths: attrPaths,
Dependencies: dependencies,
CreateBeforeDestroy: os.CreateBeforeDestroy,
PreventRemoval: os.PreventRemoval,
}
}

Expand Down Expand Up @@ -213,6 +214,7 @@ func (o *ResourceInstanceObject) DeepCopy() *ResourceInstanceObject {
Private: private,
Dependencies: dependencies,
CreateBeforeDestroy: o.CreateBeforeDestroy,
PreventRemoval: o.PreventRemoval,
}
}

Expand Down
4 changes: 4 additions & 0 deletions internal/states/statefile/version4.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ func prepareStateV4(sV4 *stateV4) (*File, tfdiags.Diagnostics) {
obj := &states.ResourceInstanceObjectSrc{
SchemaVersion: isV4.SchemaVersion,
CreateBeforeDestroy: isV4.CreateBeforeDestroy,
PreventRemoval: isV4.PreventRemoval,
}

{
Expand Down Expand Up @@ -510,6 +511,7 @@ func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstanc
PrivateRaw: privateRaw,
Dependencies: deps,
CreateBeforeDestroy: obj.CreateBeforeDestroy,
PreventRemoval: obj.PreventRemoval,
}), diags
}

Expand Down Expand Up @@ -736,6 +738,8 @@ type instanceObjectStateV4 struct {
Dependencies []string `json:"dependencies,omitempty"`

CreateBeforeDestroy bool `json:"create_before_destroy,omitempty"`

PreventRemoval bool `json:"prevent_removal,omitempty"`
}

type checkResultsV4 struct {
Expand Down
26 changes: 22 additions & 4 deletions internal/terraform/node_resource_abstract_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,22 +165,37 @@ func (n *NodeAbstractResourceInstance) readDiff(ctx EvalContext, providerSchema
}

func (n *NodeAbstractResourceInstance) checkPreventDestroy(change *plans.ResourceInstanceChange) error {
if change == nil || n.Config == nil || n.Config.Managed == nil {
if change == nil {
return nil
}

preventDestroy := n.Config.Managed.PreventDestroy
preventDestroy := false

// if prevent_destroy is set, then we should prevent destroy
if n.Config != nil && n.Config.Managed != nil {
preventDestroy = preventDestroy || n.Config.Managed.PreventDestroy
}

// if there's a prevent_removal is true in the state, then we should prevent destroy
if n.instanceState != nil && n.instanceState.Current != nil {
preventDestroy = preventDestroy || n.instanceState.Current.PreventRemoval
}

if (change.Action == plans.Delete || change.Action.IsReplace()) && preventDestroy {
var subject *hcl.Range
if n.Config != nil {
subject = &n.Config.DeclRange
}

var diags tfdiags.Diagnostics
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Instance cannot be destroyed",
Detail: fmt.Sprintf(
"Resource %s has lifecycle.prevent_destroy set, but the plan calls for this resource to be destroyed. To avoid this error and continue with the plan, either disable lifecycle.prevent_destroy or reduce the scope of the plan using the -target flag.",
"Resource %s has lifecycle.prevent_destroy or had lifecycle.prevent_removal set, but the plan calls for this resource to be destroyed. To avoid this error and continue with the plan, either disable lifecycle.prevent_destroy and lifecycle.prevent_removal, or reduce the scope of the plan using the -target flag.",
n.Addr.String(),
),
Subject: &n.Config.DeclRange,
Subject: subject,
})
return diags.Err()
}
Expand Down Expand Up @@ -2284,6 +2299,7 @@ func (n *NodeAbstractResourceInstance) apply(
if change.Action == plans.Update && eq && !marksEqual(beforePaths, afterPaths) {
// Copy the previous state, changing only the value
newState := &states.ResourceInstanceObject{
PreventRemoval: state.PreventRemoval,
CreateBeforeDestroy: state.CreateBeforeDestroy,
Dependencies: state.Dependencies,
Private: state.Private,
Expand Down Expand Up @@ -2496,6 +2512,7 @@ func (n *NodeAbstractResourceInstance) apply(
Value: newVal,
Private: resp.Private,
CreateBeforeDestroy: createBeforeDestroy,
PreventRemoval: state.PreventRemoval,
}

// if the resource was being deleted, the dependencies are not going to
Expand All @@ -2513,6 +2530,7 @@ func (n *NodeAbstractResourceInstance) apply(
Value: newVal,
Private: resp.Private,
CreateBeforeDestroy: createBeforeDestroy,
PreventRemoval: applyConfig.Managed.PreventRemoval,
}
return newState, diags

Expand Down
1 change: 1 addition & 0 deletions internal/terraform/node_resource_plan_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext)
} else {
if instanceRefreshState != nil {
instanceRefreshState.CreateBeforeDestroy = n.Config.Managed.CreateBeforeDestroy || n.ForceCreateBeforeDestroy
instanceRefreshState.PreventRemoval = n.Config.Managed.PreventRemoval
}
}

Expand Down

0 comments on commit 1959d25

Please sign in to comment.