hashicorp / terraform

Terraform enables you to safely and predictably create, change, and improve infrastructure. It is a source-available tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned.
https://www.terraform.io/
Other
42.52k stars 9.52k forks source link

Feature request: support prevent_destroy for modules #18367

Open tdmalone opened 6 years ago

tdmalone commented 6 years ago

Terraform Version

Terraform v0.11.7

Terraform Configuration Files

module "test" {
  source = "../modules/module-example"

  lifecycle {
    prevent_destroy = true
  }
}

Expected Behavior

I was hoping I could prevent destroy of any resources created by the module.

Actual Behavior

Error: module "test": "lifecycle" is not a valid argument

References

apparentlymart commented 6 years ago

Hi @tdmalone!

The available settings inside lifecycle will vary depending on the type of block it's used in, so I adjusted the summary of this issue to be specifically about supporting prevent_destroy on modules, since that's a tighter scope to think about and design for.

I assume that your intent here was this to behave as if prevent_destroy were set to true for every resource inside the module, regardless of what it's actually set to. This does seem like a nice idea since it means that the decision can be made by the caller rather than the module author, and can thus represent a situation where e.g. a particular module must be destroyable in one configuration but not another.

We won't be able to work on this immediately due to other work in progress, but I like the idea of it and would like to dig into it a little more and see what the implementation might look like some time after the next major release (which is already pretty large in scope at this point).

Thanks for this suggestion!

Constantin07 commented 5 years ago

Any plans to include this in 0.12 release ?

flickerfly commented 5 years ago

Is there a ticket to support ignore_changes on the module lifecycle also that I haven't found yet? That'd be handy.

tdmalone commented 5 years ago

@flickerfly I haven’t seen one, if one doesn’t already exist you could create one. I would imagine implementing that would be pretty.... interesting.... though!

solinas-http commented 5 years ago

Any plans to include this in 0.12 release ?

Looking forward to this feature as well.

dbektas commented 4 years ago

@apparentlymart are there plans for this improvement any time soon? Find it extremely useful.

eddideku commented 4 years ago

I have specific modules that I'd like to prevent_destroy and ignore

Unfortunately there has been drift and TF is marking them to destroy (forces new resource)

mkjmdski commented 4 years ago

@apparentlymart is there any update on this? It is already after major release and it seems like terraform 0.12 really misses that feature to allow moving modules between various environments (qa -> prod) where in one infrastructure should terraform should be able to destroy module and on another no destroy action is welcomed ;).

abdennour commented 4 years ago

Terraform v0.12.28 but it is still not released

 on main.tf line 53, in module "dns":
  53:   lifecycle {

The block type name "lifecycle" is reserved for use by Terraform in a future version.

I also tried to pass variable for the module

# main.tf
module "dns" {
  source = "./modules/dns"
  prevent_destroy = true
}

# modules/dns/main.tf
resource "aws_route53_zone" "publiczone" {
  lifecycle {
    prevent_destroy = var.prevent_destroy
  }
 comment = "..."
}

Getting :

   3:     prevent_destroy = var.prevent_destroy

Unsuitable value: value must be known

No pb! please let me violate DRY.

jpbuecken commented 4 years ago

Our Use Case: We build X instances derived from a module. During daily tasks, they should be prevented from destroy. Now one instance should be deleted. We want to disable the protection for this specific instance, then destroy in an uninstallation process. Solution of OP (rewrite hcl) or https://github.com/hashicorp/terraform/issues/18367#issuecomment-653938931 (via variable) will work

politician commented 4 years ago

@apparentlymart 0.13 is out and this feature is still not included in the release even though it has received a lot of love over the years. Now that modules support for_each, this feature is even more pressing to finally truly use modules just like any other resource.

Any plan to integrate it soon?

By the way for other users stuck with this, there is a (dirty) workaround which is not scalable at all but might work for small modules: You can put a condition at the resource level rather than at the block level. It's not scalable because you'd have to duplicate any resource you want to protect and use conditional expressions whenever you want to reference them. And if you eventually want to destroy these resources, you will need to remove the _preventdestroy from the module code.

For example, to take @abdennour's example

# main.tf
module "dns" {
  source = "./modules/dns"
  prevent_destroy = true
}

# modules/dns/main.tf
resource "aws_route53_zone" "publiczone" {
  count = var.prevent_destroy ? 0 : 1
  comment = "..."
}

# modules/dns/main_protected.tf
resource "aws_route53_zone" "publiczone_protected" {
  count = var.prevent_destroy ? 1 : 0
  lifecycle {
    prevent_destroy = true
  }
  comment = "..."
}
Luminoth commented 3 years ago

Would really love to see this feature implemented to help guard against accidentally deleting an environment worth of resources.

rd-michel commented 3 years ago

current workflow:

  1. rollout terraform code and find out that i complains about an existing "prevent_destroy"
  2. raise a PR to set prevent_destroy=false for e.g. all existing gcp clusters just to change one cluster
  3. waiting 30 min for review/approval of the PR
  4. rollout the code without any problems
  5. rollback the change done in step 2
  6. wait 30 min for review/approval of the PR
  7. back to normal state where every cluster is protected again

we cannot use the workaround by @politician because we cant duplicate 30 lines of code just to workaround terraform internal issues. we`re using terraform 0.15

we want to use prevent_destroy in module calls for dns, clusters, project, databases in gcp

petewilcock commented 3 years ago

Approaching 3 years old on this issue and still desperately requesting a way to prevent_destroy on modules.... πŸ™

nitmatgeo commented 3 years ago

We definitely need a solution, please refer to my proposal here.

Tagging few who seemed to have involved wrt lifecycle blocks.. please help @jbardin @apparentlymart @pkolyvas

c4m4 commented 3 years ago

Do we have any update about this?

sayujnath commented 3 years ago

I would really like this feature as well. I hope we can see this implemented soon.

aelbarkani commented 3 years ago

+1 here

NicolasKarnick commented 2 years ago

Any news on this?

jrowinski3d commented 2 years ago

This would be an amazing feature to have giving the user control on their resources instead of depending on the module author. Obviously this can be worked around via creating your own modules, but does create overhead. +1 for seeing this come to light

slawekzachcial commented 2 years ago

I put together an example that shows how to, for certain use cases, protect resources created by 3rd party modules.

In a nutshell, assuming your configuration uses a 3rd party module (e.g. module.security_group) that has output(s) that changes when the resource(s) gets re-created (e.g. group_id), add the following resource:

resource "null_resource" "module_guardian" {
  triggers = {
    security_group_id = module.security_group.group_id
  }

  lifecycle {
    prevent_destroy = true
  }
}

The triggers value creates implicit dependency. When calling terraform destroy Terraform will first attempt to destroy null_resource.module_guardian and this will fail due to prevent_destroy value. Also, in the example above, when the Terraform plan indicates that security group ID changes (happens when security group is re-created), Terraform will attempt to re-create null_resource.module_guardian and this will fail as well.

Finally, null_resource.module_guardian resource can be created conditionally, by adding count expression, to protect for example "production" module resource(s) but not "development":

resource "null_resource" "module_guardian" {
  count = var.env == "pro" ? 1 : 0

  triggers = {
    security_group_id = module.security_group.group_id
  }

  lifecycle {
    prevent_destroy = true
  }
}
joshfria commented 2 years ago

Is this in the plans at all? Without it reusing code seems nearly impossible. The above solution works, but adds complication when you do actually need to destroy that resource later.

asifhj commented 2 years ago

Team, any plans to consider this feature soon.

james-green-affinity commented 2 years ago

Lets please get this in! :)

dpolivaev commented 2 years ago

Read the docs:

"Since this argument must be present in configuration for the protection to apply, note that this setting does not prevent the remote object from being destroyed if the resource block were removed from configuration entirely: in that case, the prevent_destroy setting is removed along with it, and so Terraform will allow the destroy operation to succeed."

https://www.terraform.io/language/meta-arguments/lifecycle

It definitely makes no sense.

RootHopper commented 1 year ago

People use custom terraform modules to provision entire clusters of primary-source-of-truth databases.

To place a prevent_destroy at every resource in the custom_module makes it very hard to test changes downstream, necessitating multiple pushes across both the calling context in your main repo and the modular context in your custom module repo, if your setup is like mine.

I mean, you don't even get a plan if a single resource in a custom module protected by prevent_destroy would be destroyed by your HCL changes. Instead, you have to push a change to your custom module, then do the work you actually set out to do, then hopefully remember to change the module back when you are done, which is error-prone and joy-destroying. Not to mention all the peer review cycles, etc., interspersed throughout this cumbersome process.

Modular programming shouldn't have to work this way, and it makes me cringe at my job when I should really be having fun using an efficient tool that doesn't work against me.

In my opinion, this feature is critical for the continued adoption of Terraform.

BenB196 commented 1 year ago

I think that this feature would be valuable, but I think something that hasn't really been brought up is that prevent_destroy doesn't actually prevent destruction on removal (#17599), which when it comes to deployments and module consumption, I think is a far more likely risk.


To provide some additional feedback/insight here, below is an example of how I currently protect deployment modules/resources from accidentally being removed and therefore destroying things.

Note: This solution is not pure Terraform, as I'm not sure if there is really a way to do this with just Terraform.

Supposes we have a structure like so:

.
β”œβ”€β”€ deployments
β”‚      └── deployment-a.tf
└── modules
        └── module-a

deployment-a.tf

module "module_a" {
  source "../modules/module-a"
}

module-a/main.tf

resource "something" "something" {
  # contents
}

Currently, if we were to remove module "module-a" { from deployment-a.tf, it will delete resource "something" "something" {.

The current way I prevent this is to change the contents of deployment-a.tf to:

module "protected_module_a" {
  source "../modules/module-a"
}

(We've now prefixed the module definition with protected_).

This is the script I use to prevent "protected" definitions from being deleted: Note: This script is run a simplification of a CI/CD pipeline used.

#!/bin/bash
set -e

# Run Terraform plan as normal, and generate a local plan file.
terraform plan -out=plan.cache

# Take the generated plan file and create another file, but this time in JSON
terraform show -json plan.cache > plan.cache.json

# This is where we check for deleted protected resources
# 1. Use `jq` to parse our generated plan JSON file
# 2. `jq` looks for any `resource_changes` with the `action_reason ` "delete_because_no_resource_config"
# 3. `jq` then looks for any of those findings that has the address which starts with `^(module.)?protected_`
#    - Note: `module.` is optional in the event you define actual resources in deployment-a.tf as well
# 4. Store a list of all matching addresses to a temp file named `changes`.
cat plan.cache.json | jq -r '.resource_changes[] | select( .action_reason == "delete_because_no_resource_config" ) | select( .address | match( "^(module.)?protected_" ) ) | .address' > changes

# Get the number of protected_ things being removed.
change_count=$(cat changes | wc -l)

# Check if the count is greater than 0
if [ $change_count -ne 0 ];
then
  # print some helpful info
  echo "Error: Protected Resources are Removed Ungracefully."
  echo "Resources affected:"
  cat changes
  # exit 1 (error) to prevent anything further
  exit 1
fi

The way you'd need to properly remove a protected_ definition, is now:

  1. Rename module "protected_module-a" {
    • This can be done safely by renaming then module and using the moved block.

deployment-a.tf

- module "protected_module_a" {
-   source "../modules/module-a"
- }
+ module "module_a" {
+   source "../modules/module-a"
+ }
+ moved {
+  from = module.protected_module_a
+  to   = module.module_a
+ }
  1. Run terraform apply to apply the rename/moved
  2. Remove the resources

deployment-a.tf

- module "module_a" {
-   source "../modules/module-a"
- }
- moved {
-  from = module.protected_module_a
-  to     = module.module_a
- }
  1. Run terraform apply to remove the resources without the safety check stopping it.

By gating the ability to remove "protected" definitions behind 2 applies, and some additional state manipulation, it becomes an intentional act to remove something "protected" then an accidental one.

zefir01 commented 1 year ago

+1

mloskot commented 11 months ago

Five years mark has passed for this idea. Would there be any chance to receive any update and referring to the initial excitement by @apparentlymart in https://github.com/hashicorp/terraform/issues/18367#issuecomment-401970282 ?

EddyMaestroDev commented 11 months ago

Looking forward to this feature as well.

slnw commented 9 months ago

me to :/

bschaatsbergen commented 8 months ago

I'd be glad to take a stab at this. I can't promise quick results, but judging from the comments on this issue, the community is really looking forward to seeing this supported. Also, I see this as a nice opportunity to get to know the module part of the codebase better. @apparentlymart, does your view on this feature remain the same today?

I've went ahead and opened a draft PR, I'll link it to this PR whenever it's starting to look like something πŸ˜‰.

Syto16 commented 7 months ago

+1

LeoAnt02 commented 7 months ago

+1

thundering-herd commented 3 months ago

+1