DEV Community

James
James

Posted on

Making API calls from Terraform

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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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}"
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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)