Terraform is a powerful and intuitive tool for managing infrastructure as code. It has excellent community support with a wide range of available providers from their registry. That said, sometimes a provider may not be up-to-date with the API they interact with, meaning that we can't provision the infrastructure as we like without manual intervention.
One stopgap (until the provider releases an update) is to call the API yourself from Terraform.
The easiest way to do this is to call a script using null-resource and local-exec
resource "null_resource" "update-my-resource" {
provisioner "local-exec" {
command = "node modules/myProvider/do-update.js ${provider_module.my-resource.id}"
environment = {
CLIENTID = var.client_id
CLIENTSECRET = var.client_secret
}
}
}
We won't go into the details of the (Node.js) script referenced in the above example, but the script effectively invokes an API and manages the response. In the above example it takes an argument, the ID of a resource previously created.
Unfortunately when working with Terraform Cloud Node.js and other script runners aren't available without installation.
Thankfully we can achieve the same using curl
and a few Terraform helpers.
The example below demonstrates a multiple step flow, in this case a client credential flow.
Once again, we can use null_resource
and local-exec
, but this time we'll call out to the API using curl
. We'll use triggers
to specify an arbitrary set of values, that if changed will cause the resource to be replaced. The path defined in filename
will be used to store the content of the API call for the following step.
resource "null_resource" "get_token" {
triggers = {
filename = "${path.module}/_temp_token.txt"
}
provisioner "local-exec" {
command = <<EOT
curl -X POST https://${var.domain}/oauth/token \
-H 'content-type: application/x-www-form-urlencoded' \
-d 'audience=https://${var.domain}/api/&grant_type=client_credentials&client_id=${var.client_id}&client_secret=${var.client_secret}' \
>${self.triggers.filename}
EOT
}
}
We can then use local_file to create a reference to the files content. It'll also ensure that the provisioner completes before Terraform reads the file.
data "local_file" "token" {
filename = "${null_resource.get_token.triggers.filename}"
}
One we have the data from the first API call we can call the second. This example uses jsondecode to extract an access token needed for the subsequent call.
resource "null_resource" "update-resource" {
provisioner "local-exec" {
command = <<EOT
curl -X PATCH https://${var.domain}/api/thing/${provider_module.my-resource.id} \
-H "Authorization: Bearer ${jsondecode(data.local_file.token.content).access_token}" \
-H "Content-Type: application/json" \
-d '{"foo":"bar"}'
EOT
}
}
This is a "hack" to get around what is hopefully a short-term limitation, but it's useful to know that these options are available.
Top comments (0)