Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RetainOnDelete resource option #8746

Merged
merged 36 commits into from
Feb 16, 2022
Merged

Add RetainOnDelete resource option #8746

merged 36 commits into from
Feb 16, 2022

Conversation

Frassle
Copy link
Member

@Frassle Frassle commented Jan 13, 2022

Description

Adds a new resource option "RetainOnDelete" which if true skips calling into the provider to actually delete the resource. Instead the engine simply drops it from our state file (or replaces it in the state file in the case of replaces). Users must be aware that this "leaks" cloud resources as Pulumi is no longer tracking them, but that is the whole point of this option.

Fixes #7747 & #8450

Checklist

  • I have added tests that prove my fix is effective or that my feature works
  • Yes, there are changes in this PR that warrants bumping the Pulumi Service API version

@Frassle Frassle changed the title Fraser/retain on delete Add RetainOnDelete resource option Jan 13, 2022
@@ -1,5 +1,7 @@
### Improvements

- [sdk] - Add `RetainOnDelete` as a resource option.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open to name suggestions if anyone thinks RetainOnDelete isn't clear enough. Another idea was "OrphanOnDelete".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer RetainOnDelete over OrphanOnDelete.

Note this also aligns with CloudFormation’s similar feature: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html. ARM and Terraform don’t have this feature.

Were there other candidate options we should consider?

@@ -166,7 +166,7 @@ func (i *importer) getOrCreateStackResource(ctx context.Context) (resource.URN,
typ, name := resource.RootStackType, fmt.Sprintf("%s-%s", projectName, stackName)
urn := resource.NewURN(stackName, projectName, "", typ, tokens.QName(name))
state := resource.NewState(typ, urn, false, false, "", resource.PropertyMap{}, nil, "", false, false, nil, nil, "",
nil, false, nil, nil, nil, "")
nil, false, nil, nil, nil, "", false)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably add a way to set retainOnDelete for imports, does that make sense?

@Frassle
Copy link
Member Author

Frassle commented Jan 13, 2022

Further thinking about this I'm wondering if this should be more closely related to our current External concept? Maybe that should be a tristate of "managed", "external", "managed but used elsewhere so don't delete"?

@codecov
Copy link

codecov bot commented Jan 13, 2022

Codecov Report

Merging #8746 (205759c) into master (6a8f7be) will decrease coverage by 0.01%.
The diff coverage is 69.23%.

❗ Current head 205759c differs from pull request most recent head 2feb6b6. Consider uploading reports for the commit 2feb6b6 to get more accurate results

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #8746      +/-   ##
==========================================
- Coverage   59.36%   59.34%   -0.02%     
==========================================
  Files         643      637       -6     
  Lines       99898    97776    -2122     
  Branches     1420     1387      -33     
==========================================
- Hits        59309    58030    -1279     
+ Misses      37206    36468     -738     
+ Partials     3383     3278     -105     
Impacted Files Coverage Δ
pkg/backend/snapshot.go 83.03% <0.00%> (-0.85%) ⬇️
...k/dotnet/Pulumi/Resources/ResourceOptions_Merge.cs 0.00% <0.00%> (-100.00%) ⬇️
sdk/go/common/apitype/core.go 100.00% <ø> (ø)
sdk/go/pulumi/resource.go 80.92% <0.00%> (-1.71%) ⬇️
sdk/nodejs/resource.ts 30.31% <ø> (-1.71%) ⬇️
...dk/python/lib/pulumi/runtime/proto/resource_pb2.py 100.00% <ø> (ø)
sdk/python/lib/pulumi/runtime/resource.py 44.95% <ø> (+0.86%) ⬆️
sdk/nodejs/proto/resource_pb.js 40.30% <50.00%> (+0.10%) ⬆️
sdk/python/lib/pulumi/resource.py 49.73% <66.66%> (-0.53%) ⬇️
sdk/proto/go/resource.pb.go 49.20% <80.00%> (+0.24%) ⬆️
... and 190 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6a8f7be...2feb6b6. Read the comment docs.

@t0yv0 t0yv0 self-requested a review January 19, 2022 20:53
@t0yv0
Copy link
Member

t0yv0 commented Jan 24, 2022

Will be reviewing this tomorrow. There's a bit of conflict that crept in also FYI.

@Frassle
Copy link
Member Author

Frassle commented Jan 24, 2022

Will be reviewing this tomorrow. There's a bit of conflict that crept in also FYI.

I'm going to see about working in @AaronFriel idea of different delete behaviors so probably makes sense to hold off for now. I'll give you a nudge when I think it's ready again.

} else if s.old.DeleteBehaviour == resource.DeleteBehaviourDrop {
// Deleting a "drop on delete" is a no-op as the user has explicitly asked us to not delete the resource.
// But we want to diag warn that Pulumi has stopped tracking this resource
msg := fmt.Sprintf("Reference to %s dropped, Pulumi has not deleted this resource", s.old.ID)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open to better wording on this message. Should it be even more clear about the cost implications?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup this may be a bit tricky. "Pulumi dropped the reference to %s is no longer tracking it, but it remains provisioned in the cloud"? Except it's not necessarily cloud. "remains provisioned?"

const (
DeleteBehaviourDelete DeleteBehaviour = DeleteBehaviour(pulumirpc.DeleteBehaviour_DELETE)
DeleteBehaviourDrop = DeleteBehaviour(pulumirpc.DeleteBehaviour_DROP)
DeleteBehaviourProtect = DeleteBehaviour(pulumirpc.DeleteBehaviour_PROTECT)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Protect here is the same as the protect flag. We could with 4.0 remove the protect bool flag and just use this. Also scope here to add a new Protect mode that doesn't even allow replacement (the current protect will allow resources to be replaced, just not fully deleted). Then again maybe we should split this behavior flag into DeleteBehaviour (i.e. what to do for a Delete) and ReplaceBehaviour (what to do for a Replace)? Like would it be valid to say delete replacements but drop the final delete? Protect against replacements but allow the final delete?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah so it's exact same. Er, should we just use protect flag? I'm a bit too conservative perhaps - use what's out there already without changing.

@Frassle Frassle marked this pull request as ready for review February 11, 2022 09:55
@mikhailshilkov
Copy link
Member

Let's not merge this until we finalize the discussion in #7747. Let me know if I need to help drive to conclusion.

@@ -337,26 +337,34 @@ func (s *DeleteStep) Logical() bool { return !s.replacing }
func (s *DeleteStep) Apply(preview bool) (resource.Status, StepCompleteFunc, error) {
// Refuse to delete protected resources (unless we're replacing them in
// which case we will of checked protect elsewhere)
if !s.replacing && s.old.Protect {
if !s.replacing && (s.old.Protect || s.old.DeleteBehaviour == resource.DeleteBehaviourProtect) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeating a qn here - Protect and DeleteBehaviorProtect seem awfully similar, are they different somehow that we want both?

@@ -337,26 +337,34 @@ func (s *DeleteStep) Logical() bool { return !s.replacing }
func (s *DeleteStep) Apply(preview bool) (resource.Status, StepCompleteFunc, error) {
// Refuse to delete protected resources (unless we're replacing them in
// which case we will of checked protect elsewhere)
if !s.replacing && s.old.Protect {
if !s.replacing && (s.old.Protect || s.old.DeleteBehaviour == resource.DeleteBehaviourProtect) {
return resource.StatusOK, nil, fmt.Errorf("unable to delete resource %q\n"+
"as it is currently marked for protection. To unprotect the resource, "+
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the message to "remove protect flag" still apply if the cause is DeleteBehaviorProtect?

@t0yv0
Copy link
Member

t0yv0 commented Feb 15, 2022

our current External concept? Maybe that should be a tristate of "managed", "external", "managed but used elsewhere so don't delete"?

Any docs refs on this concept?

@t0yv0
Copy link
Member

t0yv0 commented Feb 15, 2022

Looked through the code, implementation is fine, just wondering about the UX on these option design, left some questions.

@@ -482,6 +482,11 @@ class ResourceOptions:
require replacement instead of update only if `"*"` is passed.
"""

retain_on_delete: Optional[bool]
"""
Option to not actually delete resources from providers when the engine calls Delete.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: try to explaining this in terms of what the user gets not how it's implemented. Taking a shot at it:

"If set to True, deletions retain the resource it the cloud instead of deleting it. Pulumi simply stops tracking the deleted resource."

@@ -322,6 +322,7 @@ func SerializeResource(res *resource.State, enc config.Encrypter, showSecrets bo
Aliases: res.Aliases,
ImportID: res.ImportID,
SequenceNumber: res.SequenceNumber,
RetainOnDelete: res.RetainOnDelete,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So.. this means the flag is to be stored in the state file correct?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has to be. Does mean if you deploy with an old CLI it will reset back to false, but we can't get this information from anywhere else for Deletes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm okay I'd think for downgrading CLI we could potentially even accept "undefined behavior" here, but your'e saying even then we still need it stored here.

Also curious for my understanding - when we extend ResourceV3, the state backends like "service" just store the extra data, the schema is fully controlled by the code here? We don't need more changes?

Copy link
Member

@t0yv0 t0yv0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with nits and final question on whether statefile persist of the flag should be avoided or not (see the ticket).

@Frassle Frassle merged commit 5d91f8f into master Feb 16, 2022
@pulumi-bot pulumi-bot deleted the fraser/retainOnDelete branch February 16, 2022 22:11
abhinav pushed a commit to pulumi/pulumi-dotnet that referenced this pull request Jan 11, 2023
* Plumb in basics of retainOnDelete

* Add test

* Make test pass

* Add to changelog

* Add to API list

* lint

* Add semicolon

* Fix Infof call

* Fix method call

* new delete mode work

* cleanup

* protectTest

* Fix up test

* Fix replace

* Fix up test

* Warn on drop

* lint

* Change to just a bool flag

* Regenerate proto

* Rework to just a bool flag with no error

* Remove old comment

* Fix C# typo

* rm extra space

* Add missing semicolon

* Reformat python

* False typo

* Fix typo in js function name

* Reword docs

* lint

* Read doesn't need retainOnDelete
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add RetainOnDelete resource option
4 participants