hashicorp / hashicorp/web-unified-docs
Resource promoted to create-before-destroy despite lifecycle setting it to false
- Dominant language
- MDX
- Stars
- 86
- Forks
- 275
- Avg merge
- 3d 18h
- Merged PRs (30d)
- 199
Description
Hello and Happy New Year!
When coupling resources through a `local` variable, the lifecycle `create_before_destroy = true` property can be propagated from a resource to another one, even if the other explicitly set it to `false`. This is hard to notice and can cause failure when applying a real plan.
It is also highly unexpected because your documentation states (https://www.terraform.io/docs/configuration/meta-arguments/lifecycle.html#create_before_destroy):
> you must understand the constraints for each resource type before using create_before_destroy with it
So having any resource spontaneously promoted to `create_before_destroy = true` doesn't fit with it being an "understood constraint". It definitely was a hard problem to diagnose and coming up with an easily reproducible test case wasn't as trivial as it seems when looking at it. :-)
### Terraform Version
```
Terraform v0.14.2
+ provider registry.terraform.io/hashicorp/null v3.0.0
```
### Terraform Configuration Files
```terraform
resource "null_resource" "cbd" {
lifecycle {
create_before_destroy = true
}
provisioner "local-exec" {
command = "echo foo: ${local.foo["bar"]}"
}
}
resource "null_resource" "dbc" {
lifecycle {
create_before_destroy = false
}
}
locals {
foo = {
bar = "bar"
dbc = {
id = null_resource.dbc.id
}
}
}
```
### Debug Output
[plan.err.log](https://github.com/hashicorp/terraform/files/5801908/plan.err.log)
[plan.out.log](https://github.com/hashicorp/terraform/files/5801909/plan.out.log)
### Expected Behavior
```
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# null_resource.dbc is tainted, so must be replaced
-/+ resource "null_resource" "dbc" {
~ id = "2486858154953534597" -> (known after apply)
}
Plan: 1 to add, 0 to change, 1 to destroy.
```
### Actual Behavior
```
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+/- create replacement and then destroy
Terraform will perform the following actions:
# null_resource.dbc is tainted, so must be replaced
+/- resource "null_resource" "dbc" {
~ id = "2486858154953534597" -> (known after apply)
}
Plan: 1 to add, 0 to change, 1 to destroy.
```
Notice the operations order that is reversed compared to the expected behaviour: `+/-` vs `-/+`.
### Steps to Reproduce
From the configuration above, do:
1. `terraform init`
2. `terraform apply`
3. `terraform taint null_resource.dbc`
4. `terraform plan`
### Additional Context
I actually encountered this behaviour in a much more complex setup of course. My actual `local.foo` is a map from server name to ip addresses and its keys are used to compute the fully qualified domain name of the servers which are in turn used to configure some of them (there is no circular dependencies between the servers). Some of the servers have an EBS volume attached and so they must be destroyed before being created otherwise the attachment of the EBS volume to the new instance will fail because it's still registered as being attached to the old instance. But despite setting explicitly `create_before_destroy = false`, terraform insists on creating the new instance first.
I eventually worked around the problem by splitting my equivalent of `local.foo` into two variables; something that can be reproduced in the test configuration above by replacing the `locals` block by:
```terraform
locals {
foo = {
bar = "bar"
}
baz = {
dbc = {
id = null_resource.dbc.id
}
}
}
```
I can imagine that it would be tricky to keep track of all the dependencies at the maps key level, which would fix the problem as the coupling is introduced by `local.foo["dbc"]` and not `local.foo["bar"]`. However, it should be possible to keep track of the fact the `create_before_destroy` is explicitly set to `false` on `dbc` and fail the plan when a conflict is encountered between a resource explicit lifecycle setting and the one inferred through dependencies. I think this would be a very valuable safe guard.
It might also be useful to indicate in the plan which resource where promoted to create-before-destroy and report in the plan summary how many of these are present to draw attention to this potentially dangerous behaviour change (for instance, starting a server using a software with limited license seats can make it non-functional if the last seat is already taken by the one that must be destroyed).
Contributor guide
Research direction
The report names no repository files or tests; start with the Terraform configuration and run init, apply, taint null_resource.dbc, and plan to reproduce the ordering. Done means the plan honors the explicit create_before_destroy = false setting for dbc instead of promoting it through the local dependency.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- terraform
- Domain
- infrastructure
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100