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 May 5, 2024
1 parent 1f91ad5 commit d72740a
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 @@ -171,6 +171,7 @@ func (os *ResourceInstanceObjectSrc) DeepCopy() *ResourceInstanceObjectSrc {
AttrSensitivePaths: attrPaths,
Dependencies: dependencies,
CreateBeforeDestroy: os.CreateBeforeDestroy,
PreventRemoval: os.PreventRemoval,
}
}

Expand Down Expand Up @@ -207,6 +208,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 @@ -509,6 +510,7 @@ func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstanc
PrivateRaw: privateRaw,
Dependencies: deps,
CreateBeforeDestroy: obj.CreateBeforeDestroy,
PreventRemoval: obj.PreventRemoval,
}), diags
}

Expand Down Expand Up @@ -722,6 +724,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 @@ -185,22 +185,37 @@ func (n *NodeAbstractResourceInstance) readDiff(ctx EvalContext, providerSchema
}

func (n *NodeAbstractResourceInstance) checkPreventDestroy(change *plans.ResourceInstanceChange) tfdiags.Diagnostics {
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 option.",
"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 option.",
n.Addr.String(),
),
Subject: &n.Config.DeclRange,
Subject: subject,
})
return diags
}
Expand Down Expand Up @@ -2438,6 +2453,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 @@ -2674,6 +2690,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 @@ -2691,6 +2708,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 @@ -199,6 +199,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 d72740a

Please sign in to comment.