Skip to main content

Upgrading Terraform Resources

One of the projects I currently work on consists of a few components - an API, a worker process, a couple of web applications and a serverless functions app - all of which are deployed to Azure using infrastrure as code defined as Terraform resources.

We have recently been upgrading it from .NET 6 to .NET 8 and hit an issue when we tried to update the framework definition of the Azure app service resources defined in Terraform. The attribute in question only accepted up to .NET 6. The reason is that the resource we were using - azurerm_app_service - is now deprecated and replaced with azurerm_windows_web_app (or azurerm_linux_web_app). It was a similar case for the app service plan (azurerm_app_service_plan replaced with azurerm_service_plan) and the functions app (azurerm_function_app replaced with azurerm_windows_function_app).

Naively, my first thought was to just update our infrastructure configuration to use the newer resources, and deploy that update. However, don't do that. What happens is that instead of doing an in-place update of the resources in Azure, it first destroys and then creates them. Fortunately I discovered this in our dev environment rather than production.

Instead the documented approach is to work locally whilst connected to the Terraform state for the environment in question. In our case, the state is held in Azure blob storage. First step is to remove the resource from the state. It stil exists in Azure of course, but Terraform no longer has any knowledge of it so effectively it becomes an unmanaged resource. With the configuration updated to use the newer resources, we can then import them back into the state file. Then it's a case of checking the planned infrastructure update, confirming only expected updates are made without any destroying and recreeating, and pushing the update to the enviorment. We then repeat for production.

Given I'll likely have a need to do it again the the future, I'll note here the steps plus a couple of gotchas I ran into.

Working in the root folder of where our infrastructure is defined, first step was to create a "vars" file holding details of where the Terraform state is found. It looked something like this:

resource_group_name = "***" storage_account_name = "***" container_name = "***" key = "***" access_key = "***"

Then initiate Terrform with:

terraform init -backend-config="vars.txt" terraform validate terraform state list

The resulting list shows all the resources currently managed by Terraform and held within the state file we've connected to in blob storage.

Now we need to find the IDs for each of the resources we want to upgrade from their deprecated versions. You can either do this via the Azure portal, or use a Terraform command to see the full details of the tracked resource:

terraform state show [resource name]

If you use the Azure portal, just watch out for casing. I was scratching my head for a bit with an "Expected a AppService ID that matched..." error, which was due to the "resourceGroups" segment of the ID being lower-cased, but it's expected as camel cased when using the Terraform CLI.

Just as a backup against disasters, using Azure storage explorer I first took a copy of the Terraform state file, so if necessary we could restore to this last known good copy.

Then remove the existing resource stored under the deprecated name and import under the new one, e.g.:

# App service plan terraform state rm azurerm_app_service_plan.asp terraform import azurerm_service_plan.asp /subscriptions/.../resourceGroups/.../providers/Microsoft.Web/serverFarms/... # App service terraform state rm azurerm_app_service.azapp-web terraform import azurerm_windows_web_app.azapp-web /subscriptions/.../resourceGroups/.../providers/Microsoft.Web/sites/... # Function app service terraform state rm azurerm_function_app.azapp-functions terraform import azurerm_windows_function_app.azapp-functions /subscriptions/.../resourceGroups/.../providers/Microsoft.Web/sites/...

Planning the terraform should show only expected changes (no destroys and creates):

terraform.plan

As part of the process, I also updated the version of Terraform we were using, which led to a couple of errors where the state contained attributes that were no longer supported. They were on resources that we weren't looking to update. Errors were of the form "Invalid resource instance data in state". The solution here was to update the state file directly.

This can be done either using further Terraform commands, or by downloading, amending and reuploading the state file - which is in JSON format - into blob storage.

terraform state pull > state.json terraform state push state.json

Comments