Thursday, March 30, 2023

IBM Cloud and Terraform: How to use a REST API

I am a regular user of the IBM Cloud provider plugin for Terraform. I use Terraform with the command line on my local machine or with IBM Cloud Schematics as managed service. Often, I am testing and evaluating new features. Sometimes, I face the situation where not all necessary functionality is available in Terraform. But luckily, often there exists already an API function. In this blog post I am going to show how to use a REST API with everything else done in Terraform.

Authentication for IBM Cloud APIs

If you have worked with APIs, you know that most of them require authentication. Often, an access token in the form of a bearer token needs to be passed. For the IBM Cloud APIs require an identity and access management (IAM) token. The information is passed in the Authorization header of a request. 

The IBM Cloud provider plugin for Terraform includes a data source to retrieve the current IAM access token. It can be used to obtain the input for the authorization header.

data "ibm_iam_auth_token" "tokendata" {}

REST API provider

There are different ways on how to call a REST API from within Terraform. One option is to utilize the local-exec provisioner with curl. I opted for using the Mastercard restapi provider and its restapi_object. Once set up, it can (not always will!) handle a REST API object as Terraform resource, including create, update, and destroy operation. I have configured the provider like this.

provider "restapi" {
uri = "https://some-api.cloud.ibm.com"
write_returns_object = true
headers = {
"Content-Type" = "application/json"
"Authorization" = data.ibm_iam_auth_token.tokendata.iam_access_token
}
create_method = "POST"
}

The uri points to the base for the IBM Cloud service to use (see the API docs). The headers section use the obtained IAM access token for the Authorization.

Last in the set of resources is restapi_object. Code might look like this:

resource "restapi_object" "registry_secret" {
depends_on = [
ibm_code_engine_project.code_engine_project_instance
]
debug=true
path="/v2/projects/${ibm_code_engine_project.code_engine_project_instance.id}/secrets"
data="${jsonencode(local.regsecret)}"
id_attribute="name"
force_new = [ "name" ]
}

Above, path relates to the API function and its parameters. The sample is configured to create a Code Engine secret. Secrets are identified by their name, hence the value for id_attribute. If the name changes, a new resource needs to be created and the old destroyed (force_new).

If you are curious about what payload is passed as data, then find here the definition of the local variable:

locals {
regsecret = {
name="${var.registry_secret_name}"
format="registry"
data= {
username="iamapikey"
password="${var.ibmcloud_api_key}"
server="de.icr.io"
email="henrik@example.com"
}
}
}

Conclusions

Even, if some feature is only available via REST API, it can be used with Terraform. Above, I showed how an option to embed a call to an API function might be integrated in Terraform code for IBM Cloud. The necessary IAM access token is available, required parameters can be obtained from the API documentation.

If you have feedback, suggestions, or questions about this post, please reach out to me on Twitter (@data_henrik), Mastodon (@data_henrik@mastodon.social), or LinkedIn.