hashicorp / hashicorp/web-unified-docs

Resource promoted to create-before-destroy despite lifecycle setting it to false

Open
#792 1 comment 7 reactions 0 assignees View on GitHub
documentation Terraform CE
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.