How to update an Azure API Management API that is configured with a remote OpenApi definition using Terraform
Just show me the code!
As always, if you don’t care about the post I have uploaded the source code on my Github.
Having an endpoint that provides the content of an OpenAPI definition or to have the OpenAPI definition file directly exposed in your API is a common practice nowadays. When integrating this API with Azure API Management, it is preferable to use the remote OpenAPI definition to expose the desired API methods.
While one could certainly copy and paste the OpenAPI document into the same folder as our Terraform files and use it to create the API Management API, it’s far more maintainable to utilize the remote OpenAPI definition/endpoint for creating or updating an Azure API Management API.
But there is one problem depending on how we create the Azure API Management API with Terraform.
The next Terraform code snippet shows the simplest way to create an Azure API Management Api configured with a remote OpenAPI definition.
resource "azurerm_api_management_api" "apim_app_a_api" {
name = "app-a-webapi"
resource_group_name = azurerm_resource_group.rg.name
api_management_name = azurerm_api_management.apim.name
revision = "1"
display_name = "app-a-webapi"
path = "app-a"
protocols = ["https"]
service_url = "https://${azurerm_container_app.aca_app_a.ingress[0].fqdn}"
subscription_required = false
import {
content_format = "openapi+json-link"
content_value = "https://${azurerm_container_app.aca_app_a.ingress[0].fqdn}/swagger/v1/swagger.json"
}
}
As you can see the import
block is configured to retrieve content from the remote API /swagger/v1/swagger.json
endpoint.
However, this scenario poses a problem.
If someone makes a change to the downstream API that modifies the OpenApi content, such as exposing a new method or altering the signature of an existing one, running the terraform plan
command will reveal that Terraform is unable to detect those changes.
Consequently, it won’t update the API in the Azure API Management to reflect the modifications.
To force the update on the Azure API Management API we have 2 options available:
- Using the API Management API
revision
property. - Using the Terraform
Http
Data Resource.
For this post, I’m utilizing Azure Container Apps to host the remote API, but it could be any other service (such as Azure App Service, ACI, Azure VM, or even a service from another Cloud). The hosting platform is not relevant.
1. Using the revision property
The revision
property is used to make non-breaking API changes to your API, so you can test changes safely. The usual process involves deploying a new revision
, conducting tests, and eventually transitioning from the old to the new revision.
However, we can repurpose this property to instigate the destruction and recreation of the current exposed API on the API Management. While the original intent of the revision
property is to maintain multiple revisions simultaneously, updating the revision
property of the current API triggers the recreation of the current resource, serving our purpose in this context, because when the API gets recreated, it will fetch the updated content from the /swagger/v1/swagger.json
endpoint.
In the following code snippet, the revision
property has been modified from 1
to 2
, triggering the destruction of the existing Azure Api Management Api and the creation of a new one with the updated revision property.
Once the new Azure API Management API is created, it will fetch the latest content from the https://${azurerm_container_app.aca_app_a.ingress[0].fqdn}/swagger/v1/swagger.json
endpoint.
resource "azurerm_api_management_api" "apim_app_a_api" {
name = "app-a-webapi"
resource_group_name = azurerm_resource_group.rg.name
api_management_name = azurerm_api_management.apim.name
revision = "2"
display_name = "app-a-webapi"
path = "app-a"
protocols = ["https"]
service_url = "https://${azurerm_container_app.aca_app_a.ingress[0].fqdn}"
subscription_required = false
import {
content_format = "openapi+json-link"
content_value = "https://${azurerm_container_app.aca_app_a.ingress[0].fqdn}/swagger/v1/swagger.json"
}
}
2. Using the Terraform Http Data Resource
The Terraform http
data resource makes an HTTP GET request to a given URL and exports information about the response.
In the next code snippet, we utilize the Terraform data http
resource to retrieve the content from the remote API OpenApi definition file.
Subsequently, we use the output to configure the Azure API Management API.
data "http" "apim_app_b_openapi" {
url = "https://${azurerm_container_app.aca_app_b.ingress[0].fqdn}/swagger/v1/swagger.json"
request_headers = {
Accept = "application/json"
}
}
resource "azurerm_api_management_api" "apim_app_b_api" {
name = "app-b-webapi"
resource_group_name = azurerm_resource_group.rg.name
api_management_name = azurerm_api_management.apim.name
revision = "1"
display_name = "app-b-webapi"
path = "app-b"
protocols = ["https"]
service_url = "https://${azurerm_container_app.aca_app_b.ingress[0].fqdn}"
subscription_required = false
import {
content_format = "openapi+json"
content_value = data.http.apim_app_b_openapi.response_body
}
}
Which option is better to use?
TL;DR: Go with the Terraform
http
data resource.
The primary advantage of utilizing the Terraform http
resource is that there is no need to manually modify the revision
property in our Terraform files when someone makes a change to the remote API that modifies the Swagger content.
In the first scenario, we had to manually change the revision
property to trigger the recreation of the API Management API resource.
By employing the Terraform data http
resource, no modifications are required in our Terraform files. The http
resource ALWAYS fetchs the content from the remote OpenApi definition whenever a terraform plan
command is executed.
The following image illustrates this concept. We’ve introduced a modification to the downstream API, and when the terraform plan
command is executed, Terraform seamlessly detects the change and updates the API Management API, thanks to the data http
resource.
Also, as I have told you in the previous section, the revision
property is not exactly built for what we’re doing in here.
The revision property is used to make non-breaking API changes so you can model and test changes safely. When ready, you can make a revision current and replace your current API.
The correct usage for the revision
property is to have multiple azurerm_api_management_api
resources at the same time, each one containing a different version of the revision.
The following code snippet demonstrates how to have two revisions simultaneously in your Terraform files.
resource "azurerm_api_management_api" "apim_app_b_api" {
name = "app-b-webapi"
resource_group_name = azurerm_resource_group.rg.name
api_management_name = azurerm_api_management.apim.name
revision = "1"
display_name = "app-b-webapi"
path = "app-b"
protocols = ["https"]
service_url = "https://${azurerm_container_app.aca_app_b.ingress[0].fqdn}"
subscription_required = false
import {
content_format = "openapi+json"
content_value = data.http.apim_app_b_openapi.response_body
}
}
resource "azurerm_api_management_api" "apim_app_b_api_version_2" {
name = "app-b-webapi"
resource_group_name = azurerm_resource_group.rg.name
api_management_name = azurerm_api_management.apim.name
revision = "2"
display_name = "app-b-webapi"
path = "app-b"
protocols = ["https"]
service_url = "https://${azurerm_container_app.aca_app_b.ingress[0].fqdn}"
subscription_required = false
source_api_id = "${azurerm_api_management_api.apim_app_b_api.id};rev=1"
}
You might be wondering if you could use that approach in here. When the downstream API changes, you create a new azurerm_api_management_api
resource with a new revision
version, and this new revision
will contain the changes made to the downstream OpenAPI definition file.
No, it doesn’t work.
When you create a new revision
as shown in the code snippet above, the value of the content_value
property doesn’t get refreshed. Instead, it uses the content_value
of the previous revision, meaning that the changes made to the downstream API are not reflected in this new revision
.