Hi,
I am having issues with null provision depend_on strategies. I am trying to recreate the null provision resource if any changes happens to template file that’s used during the provision. The resources in which the template file changed performs the force replacement correctly. However the dependent resources are not recreated.
Terraform Version
0.12.26
Terraform Configuration Files
data "template_file" "disk_tmp_file" {
count = var.node_count
template = file(var.script_source_tpl)
vars = {
disk = var.disk
}
}
resource "null_resource" "first_resource" {
count = var.node_count
triggers = {
private_ip = element(var.private_ip,count.index)
data = element(data.template_file.disk_tmp_file.*.rendered,count.index)
}
connection {
host = element(var.private_ip,count.index)
type = var.login_type
user = var.user_name
password = var.instance_password
port = var.port
}
provisioner "file" {
content = element(data.template_file.disk_tmp_file.*.rendered,count.index)
destination = var.script_destination
}
}
resource "null_resource" "second_resource" {
depends_on = [var.md_depends_on]
}
module "first_module" {
source = ${resource1}
script_source_tpl = "../../scripts/${var.platform}/diskPart.tpl"
}
module "second_module" {
source = ${resource2}
md_depends_on = [module.first_module]
}
variable "md_depends_on" {
type = any
default = null
}
Expected Behavior
On template change - Should recreate both the resources - resource 1 and resource 2
Actual Behavior
On template change - Updates only the resource 1
Additional Context
Dependency works perfectly if I create or destroy resources. The problem is only if the template file changes and triggers tries to update the resource.
Hi @Dineshk77,
The depends_on
meta attribute only creates dependencies between resources, it does not force any changes upon a resource. This means if the resource has no changes, depends_on
does not have any effect.
The only way to recreate a resource automatically like this is to assign a change to a field that forces the recreation of that resource. In the null_resource
case, that is the triggers
attribute.
@jbardin In my above example (resource 2 depends on resource1) : resource 1 has changed due to a change in triggers. However the resource 2 didn’t recreate. if resource1 recreated then I believe resource 2 has to be recreated ? (correct me if I am wrong).
You will need to include a triggers
map in null_resource.second_resource
which includes a value that changes when null_resource.first_resource
is replaced.
As @jbardin noted, the depends_on
only ensures that null_resource.second_resource
will be created after null_resource.first_resource
. depends_on
alone can never cause an action to be taken: it only decides on the ordering of actions Terraform was going to take for other reasons.
For example, you could add a second_resource_triggers
input variable to your second module and then use it as the triggers:
variable "second_resource_triggers" {
type = map(string)
default = {}
}
resource "null_resource" "second_resource" {
triggers = var.second_resource_triggers
}
With that said, I can only show a contrived example because the example you shared is also contrived. If you can say more about what your final goal is – ignoring for the moment how you will describe that to Terraform – I can maybe give a different suggestion that doesn’t involve exposing the implementation detail that the second module contains a null_resource
resource.
Thanks for the detailed explanation. Understanding that depends_on attribute only decides the order of actions. However below actions produce different outcomes.
Resource details:
- null_resource 1
- null_resource 2 (depends on null_resource 1)
lets say : terraform destroy -target=null_resource1
- this will delete both resource 1 and resource 2, because they are dependent
lets say - triggers attribute changed in null_resource1
- sample trigger change plan
# module.ecs_prov_choco.null_resource.run_prov must be replaced
-/+ resource "null_resource" "null_resource1" {
~ id = "****" -> (known after apply)
~ triggers = { # forces replacement
+ "data" = <<~EOT
:InstallDependencies
@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "[System.Net.ServicePointManager]::SecurityProtocol = 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
if %errorlevel% neq 0 exit /b %errorlevel%
EOT
~ "private_ip" = "***" -> "****"
}
}
Plan: 1 to add, 0 to change, 1 to destroy.
- it just destroys only the null_resource1. isn’t supposed to destroy the dependent resource as well (in this case - null_resource2)?
May I know why the behaviour here is different?.
@apparentlymart any update on this?. It will be helpful if you can share info on the behaviour I have mentioned. Thanks
@Dineshk77,
What you are seeing here is due to the behavior of -target
with destroy
. The destroy
command indicates that terraform intends to destroy all resources, then -target
reduces the graph down to the minimum set of downstream dependencies starting at the targeted resources. This means that the plan for null_resource2
will be to destroy it.
When you run a normal plan, terraform’s goal is to make the minimum set of changes necessary, rather than the goal of destroying all resources. This means the resources specified by -target
must only retain all upstream dependencies, and the change to null_resource2
is ignored.
While the defined behavior of -target
is intended to cover the most common use cases without creating invalid situations, that behavior is still somewhat arbitrary. I believe there are open feature requests to add more options for flexibility, but without some more research it’s unclear exactly what should be implement in that area, and more likely a new workflow needs to be conceived to cover more specific use cases. I generally recommend avoiding -target
whenever possible, and using changes in the configuration itself to drive terraform.