DEV Community

Cover image for Terraforming with a Twist: Go with the (azapi) Flow
Cody Antonio Gagnon
Cody Antonio Gagnon

Posted on

Terraforming with a Twist: Go with the (azapi) Flow

Sometimes we find ourselves with new default resource configurations which are incompatible with previously deployed infrastructure. These configurations may not even exist inside of a given Terraform provider.

In this post we go over one such example I'd discovered, how I worked around the issue, and some takeaways I learned through the experience.

🚨 Hello Error

HTTP 409 meme
credit, original link:

Recently, while repurposing some code to enable vulnerability assessments on Azure SQL databases, I came across the following error:

Status=409 Code="VulnerabilityAssessmentStoragelessIsEnabled" Message="Vulnerability Assessment is enabled on this server or one of its underlying databases with an incompatible version. Additional troubleshooting can be found at

I didn't understand at the time that this actually had little to do with the resource I was trying to provision (azurerm_mssql_server_vulnerability_assessment) and more to do with how Azure now auto-enables a new type of vulnerability assessments on newly provisioned MSSQL servers.

πŸ” Sleuthing for a Fix

Upon initial search for the usual suspects (Stack Overflow, GitHub, cloud/provider documentation), I didn't come across others running into this particular error. Luckily, all was not lost. With the link from the error itself( I was able to educate myself on exactly what was happening.

This configuration was new to me, so I first needed to learn what the difference between the "express" configuration in comparison to the "classic" configuration. This was provided on the error's link here.

After some digging from the resources provided, I learned a crucial piece of info from the common questions doc:

Q: Can I choose which experience is the default?
A: No. Express configuration is the default for every new supported Azure SQL database.

Learning that this was auto-configured by default, the next step is to determine how to work through configuring this in Terraform, which led me to this GitHub issue (which essentially is simply a request for getting the express configuration of SQL
server vulnerability assessments supported in the azurerm provider.)

So this presented a dead end... or did it? ...

🚦 Decision Point - Classic, or Express?

I now needed to make a decision as to which type of Azure SQL Vulnerability Assessment to use:

  • Either the new "express" configuration (which was created outside of Terraform automatically by Azure and does not currently [as of 2/16/2024] have support directly in the azurerm provider)
  • Or somehow disable the "express" configuration (which was causing the original error) and then continue to provision the "classic" configuration

I decided that using the classic configuration (at least for now) would provide the easiest configuration for our customer. The tradeoffs we made were negligible in this case, since:

  1. Classic version has an internal scheduler for sending emails automatically, meaning we would not need to stand up a separate Logic Apps to get this functionality.
  2. Requires a storage account dependency, and this is a simple resource to provision.
  3. Other minor changes, like what the policy could apply to (Database granularity), or what the scans export format could be (classic can export to Excel).

You can read more about the differences between express and classic configuration here.

πŸ’‘ Idea & Testing - Introducing azapi

With the decision made to use classic configuration, the main question was:

How do we disable the express configuration if it's not available in the azurerm provider?

Luckily, the GitHub issue from sleuthing provided a very useful bit of code that shows it's possible to configure "express" mode simply from an ARM/Bicep template!

But wait, that's not Terraform!

Well, what if I told you that actually, there's a much simpler way to write ARM or Bicep code directly from your Terraform?

Image description

The azapi provider is pretty cool and easy to get started with. Here are some of my favorite ways it works:

  1. Can re-use the same provider credentials as used in azurerm (but doesn't have to). Theoretically, if you have Terraform setup with azurerm, it should just work out of the box with azapi!
  2. Acts as almost a shim to ARM or Bicep templates - you only need to specify the body which, thanks to jsonencode() can also be done in HCL πŸ€“. Of course, you can also just use something like a heredoc to write actual JSON from the ARM template here too.

For more info on the azapi provider, check out the official docs here

βœ… Final Implementation

You can find a full Terraform deployment here, with an abridged (sql-only) subset below:

# ---------
# ---------
resource "azurerm_mssql_server" "example" {
  name                         = "mysqlserver${random_id.sql_server.hex}"
  resource_group_name          =
  location                     = azurerm_resource_group.example.location
  version                      = "12.0"
  administrator_login          = "4dm1n157r470r"
  administrator_login_password = "4-v3ry-53cr37-p455w0rd"

resource "azurerm_mssql_server_security_alert_policy" "example" {
  resource_group_name =
  server_name         =
  state               = "Enabled"

# Use a descriptive name for the terraform resource so it is easy to infer from the plan
# and continued management of said resource
resource "azapi_update_resource" "disable_sql_vulnerability_assessments_express_configuration" {
  type      = "Microsoft.Sql/servers/sqlVulnerabilityAssessments@2022-05-01-preview"
  name      = "default"
  parent_id =
  body = jsonencode({
    properties = {
      state = "Disabled"

resource "azurerm_mssql_server_vulnerability_assessment" "example" {
  server_security_alert_policy_id =
  storage_container_path          = "${azurerm_storage_account.example.primary_blob_endpoint}${}/"
  storage_account_access_key      = azurerm_storage_account.example.primary_access_key

  recurring_scans {
    enabled                   = true
    email_subscription_admins = true
    emails = [
  # Ensure that express configuration is disabled before we provision classic
  depends_on = [azapi_update_resource.disable_sql_vulnerability_assessments_express_configuration]
Enter fullscreen mode Exit fullscreen mode

A nice graph of all the resources to be provisioned can be found below
(this was generated using terraform graph | dot -Tpng -o graph.png after installing graphviz on macOS)

Graph of Resources to Provision

πŸ”‘ Main Takeaways

  • πŸ”„ Change is inevitable
    • πŸ”§πŸš« Provisioning code that used to work may become incompatible due to updated external API changes.
  • πŸ€– azapi makes sense to use when external API changes remain to be implemented officially
    • πŸ”πŸ”„ azurerm's status of the resource/config in question's implementation due to the changes are important to consider first.
  • πŸ›‘ The best way to use azapi is not to use it at all
    • πŸ”„βž‘οΈ When azurerm is updated with the changes that were implemented in azapi, it's best to migrate resources to azurerm as it more tightly integrates with Terraform to enable features like intellisense (autocomplete and syntax validation).

🎁 Wrapping Up

I'm hopeful this was a helpful blog post on your cloud journey ☁

How have you used azapi in your past Terraform deployments? Any comments or clarifications I could use to improve this post? Please feel free to reach out in the comments section below.

All code in the post can be found here

Thanks, and happy automating ✨

Top comments (1)

ketki_hardas_dbf0847cb134 profile image
Ketki Hardas

Very well explained, thank you!