Terraform local-exec command with complex variables - terraform

I'd need to iterate through a list of variables in local-exec provider. Is that possible?
variables.tf:
variable "items" {
default = []
}
main.tf:
resource "null_resource" "loop_list" {
provisioner "local-exec" {
interpreter = ["/bin/bash", "-c"]
command = <<EOF
for i in ${join(' ', var.items)}
print $i
done
EOF
}
}

You should be able to use environment. Something like this:
variable "items" {
default = ["item1", "item2"]
}
resource "null_resource" "loop_list" {
provisioner "local-exec" {
command = "for item in $ITEMS; do echo $item >> test-file; done"
environment = { ITEMS = join(" ", var.items) }
}
}
Where terraform apply and cat test-file yields:
item1
item2

Related

How to iterate resources in terraform?

How to iterate resources for different values of a parameter
For example, in my below terraform file I have one data block and one resource. If I pass value DB_NAME=test then its working fine. But what if I have multiple values of DB_NAME and I want it to run multiple time DB_NAME=test, app. How will I iterate over data and resource block? :
data "template_file" "search-index" {
template = "${file("search-index/search-index.sh")}"
vars {
DB_NAME = "${var.DB_NAME}"
}
}
resource "null_resource" "script" {
triggers = {
DB_NAME = "${var.DB_NAME}"
script_sha = "${sha256(file("search-index/search-index.sh"))}"
}
provisioner "local-exec" {
command = "${data.template_file.search-index.rendered}"
interpreter = ["/bin/bash", "-c"]
}
}
I'm not sure if I fully understood what you are trying to achieve but you can create multiple DB by defining a variables.tf like this:
variable "DB_NAME" {
description = "A list of databases"
type = list(string)
default = ["db1", "db2", "db3"]
}
And then using the for_each functionality in your terraform file:
data "template_file" "search-index" {
for_each = toset(var.DB_NAME)
template = "${file("search-index/search-index.sh")}"
vars = {
DB_NAME = each.value
}
}
resource "null_resource" "script" {
for_each = toset(var.DB_NAME)
triggers = {
DB_NAME = each.value
script_sha = "${sha256(file("search-index/search-index.sh"))}"
}
provisioner "local-exec" {
command = "${data.template_file.search-index[each.value]}"
interpreter = ["/bin/bash", "-c"]
}
}

Terraform - multi-line JSON to single line?

I've created a JSON string via template/interpolation.
I need to pass that to local-exec, which in turn uses a Powershell template to make a CLI call.
Originally I tried just referencing the json template in the Powershell command itself
--cli-input-json file://lfsetup.tpl
.. however, the template does not get interpolated.
Next, I tried setting the json to a local. However, this is multi-line and the CLI does not like that. Maybe if I could convert to single line ?
Any sugestions or guidance welcome !!
Thanks
JSON (.tpl or variable)
{
"CatalogId": "${account_id}",
"DataLakeSettings": {
"DataLakeAdmins": [
{
"DataLakePrincipalIdentifier": "arn:aws:iam::${account_id}:role/Role1"
},
{
"DataLakePrincipalIdentifier": "arn:aws:iam::${account_id}:role/Role2"
}
],
"CreateDatabaseDefaultPermissions": [],
"CreateTableDefaultPermissions": []
}
}
.tf
locals {
assume_role_arn = "arn:aws:iam::${local.account_id}:role/role_to_assume"
lf_json_settings = templatefile("${path.module}/lfsetup.tpl", { account_id = local.account_id})
cli_region = "region"
}
resource "null_resource" "settings" {
provisioner "local-exec" {
command = templatefile("${path.module}/scripts/settings.ps1", { role_arn = local.assume_role_arn, json_settings = local.lf_json_settings, region = local.cli_region})
interpreter = ["pwsh", "-Command"]
}
}
.ps
$ErrorActionPreference = "Stop"
$json = aws sts assume-role --role-arn ${role_arn} --role-session-name sessionname
$accessTokens = ConvertFrom-Json (-join $json)
$env:AWS_ACCESS_KEY_ID = $accessTokens.Credentials.AccessKeyId
$env:AWS_SECRET_ACCESS_KEY = $accessTokens.Credentials.SecretAccessKey
$env:AWS_SESSION_TOKEN = $accessTokens.Credentials.SessionToken
aws lakeformation put-data-lake-settings --cli-input-json file://lfsetup.tpl --region ${region}
$env:AWS_ACCESS_KEY_ID = ""
$env:AWS_SECRET_ACCESS_KEY = ""
$env:AWS_SESSION_TOKEN = ""
Output:
For these I put the template output into a local and passed the local to powershell. Then did variations with/out jsonencde and trying to replace '\n'. Strange results in some cases:
Use file provisioner to create .json file from rendered .tpl file:
locals {
...
settings_json_file = "/tmp/lfsetup.json"
}
resource "null_resource" "settings" {
provisioner "file" {
content = templatefile("${path.module}/lfsetup.tpl", { account_id = local.account_id})
destination = local.settings_json_file
}
provisioner "local-exec" {
command = templatefile("${path.module}/scripts/settings.ps1", role_arn = local.assume_role_arn, json_settings = local.settings_json_file, region = local.cli_region})
interpreter = ["pwsh", "-Command"]
}
}
Update your .ps file
replace file://lfsetup.tpl by file://${json_settings}
aws lakeformation put-data-lake-settings --cli-input-json file://${json_settings} --region ${region}
You may also use jsonencode function

terraform local-exec ausführen

I would like to perform the following scenario in Terraform:
resource "aws_ecr_repository" "jenkins" {
name = var.image_name
provisioner "local-exec" {
command = "./deploy-image.sh ${self.repository_url} ${var.image_name}"
}
}
However, it is not executed. Does anyone have an idea what could be?
I had to add a working directory
resource "null_resource" "backend_image" {
triggers = {
build_trigger = var.build_trigger
}
provisioner "local-exec" {
command = "./deploy-image.sh ${var.region} ${var.image_name} ${var.ecr_repository}"
interpreter = ["bash", "-c"]
working_dir = "${path.cwd}/${path.module}"
}
}
Now it works.

Can terraforms null_resource use timeouts blocks?

I have a terraform null_resource that looks like the following
resource "null_resource" "foo" {
provisioner "local-exec" {
command = "foo.sh"
}
}
So what I would like to know is I can use timeouts with the resource as in the following
resource "null_resource" "foo" {
provisioner "local-exec" {
command = "foo.sh"
}
timeouts {
create = "60m"
delete = "2h"
}
}
I believe the null provider doesn't support timeout operations for it's resources.
However, there should be a way to model this using destroy-time provisioners.
resource "null_resource" "foo" {
provisioner "local-exec" {
command = "timeout 60m foo.sh"
}
provisioner "local-exec" {
command = "timeout 2h foo.sh"
when = "destroy"
}
}

Retrieve the value of a provisioner command?

This is different from "Capture Terraform provisioner output?". I have a resource (a null_resource in this case) with a count and a local-exec provisioner that has some complex interpolated arguments:
resource "null_resource" "complex-provisioning" {
count = "${var.count}"
triggers {
server_triggers = "${null_resource.api-setup.*.id[count.index]}"
db_triggers = "${var.db_id}"
}
provisioner "local-exec" {
command = <<EOF
${var.init_command}
do-lots-of-stuff --target=${aws_instance.api.*.private_ip[count.index]} --bastion=${aws_instance.bastion.public_ip} --db=${var.db_name}
EOF
}
}
I want to be able to show what the provisioner did as output (this is not valid Terraform, just mock-up of what I want):
output "provisioner_commands" {
value = {
api_commands = "${null_resource.complex-provisioning.*.provisioner.0.command}"
}
}
My goal is to get some output like
provisioner_commands = {
api_commands = [
"do-lots-of-stuff --target=10.0.0.1 --bastion=77.2.4.34 --db=mydb.local",
"do-lots-of-stuff --target=10.0.0.2 --bastion=77.2.4.34 --db=mydb.local",
"do-lots-of-stuff --target=10.0.0.3 --bastion=77.2.4.34 --db=mydb.local",
]
}
Can I read provisioner configuration and output it like this? If not, is there a different way to get what I want? (If I didn't need to run over an array of resources, I would define the command in a local variable and reference it both in the provisioner and the output.)
You cannot grab the interpolated command from the local-exec provisioner block but if you put the same interpolation into the trigger, you can retrieve it in the output with a for expression in 0.12.x
resource "null_resource" "complex-provisioning" {
count = 2
triggers = {
command = "echo ${count.index}"
}
provisioner "local-exec" {
command = self.triggers.command
}
}
output "data" {
value = [
for trigger in null_resource.complex-provisioning.*.triggers:
trigger.command
]
}
$ terraform apply
null_resource.complex-provisioning[0]: Refreshing state... [id=9105930607760919878]
null_resource.complex-provisioning[1]: Refreshing state... [id=405391095459979423]
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
data = [
"echo 0",
"echo 1",
]

Resources