Azure Verified Modules with only AzAPI

Azure Verified Modules with only AzAPI

Can you make a verified module with only AzAPI?

Azure Verified Modules with only AzAPI

In this post, I’m going to illustrate a thought experiment:

Can we entirely remove the AzureRM provider from an Azure Verified Module (AVM) resource module?

Most specifically, we’re going to take a module that is largely written using AzAPI (the virtual network module), and illustrate an easy way to do the common last-mile items like the shared interfaces

As a reminder, the shared interfaces for the vnet module means:

  • role assignments,
  • diagnostic settings,
  • & locks

In the AVM template, these all use AzureRM.

In a previous post I wrote about using AzAPI to manage role assignments, as I discussed this work with the AVM team, I learnt about something new… utility modules.

Utility Modules

If you’ve been following AVM, you’ll doubtless know about resource modules and pattern modules, but what’s a utility module?!

The spec says it:

…implements a function or routine that can be flexibly reused in resource or pattern modules - e.g., a function that retrieves the endpoint of an API or portal of a given environment. It MUST NOT deploy any Azure resources other than deployment scripts.

This might seem a bit counter-intuitive, a module that doesn’t deploy anything?

What is this trickery?!

As it turns out, utility modules are very useful for encapsulating logic that is needed across many resources. Every resource must support all the standard interfaces, an ideal use case for flexible re-use, hence this creation from Matt White:

https://github.com/Azure/terraform-azure-avm-utl-interfaces

Its purpose is to make it easier to support the standard interfaces.

Using the interface utility module

Calling the utility module is done like this:

1
2
3
4
5
6
7
8
module "avm_interfaces" {
  source                           = "Azure/avm-utl-interfaces/azure"
  version                          = "0.2.0"
  diagnostic_settings              = var.diagnostic_settings
  lock                             = var.lock
  role_assignments                 = var.role_assignments
  role_assignment_definition_scope = "/subscriptions/${data.azapi_client_config.this.subscription_id}"
}

The “vars” on the right-hand side are just the required variable blocks defined in the specification, as you get from the AVM template.

The utility module returns maps as outputs which can then be used to make the components:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# role assignment example
resource "azapi_resource" "role_assignment" {
  for_each = module.avm_interfaces.role_assignments_azapi

  type      = each.value.type
  body      = each.value.body
  locks     = [azapi_resource.vnet.id]
  name      = each.value.name
  parent_id = azapi_resource.vnet.id
}

The returned map makes this very easy to use, you just need to assign the type + the body.

Similarly, here’s the lock - there’s only 0 or 1 of these, so we’ll use a count:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# resource lock example
resource "azapi_resource" "lock" {
  count = module.avm_interfaces.lock_azapi != null ? 1 : 0
  
  type  = lookup(module.avm_interfaces.lock_azapi, "type", null)
  body  = lookup(module.avm_interfaces.lock_azapi, "body", null)
  locks = [azapi_resource.vnet.id]
  name      = lookup(module.avm_interfaces.lock_azapi, "name", null)
  parent_id = azapi_resource.vnet.id
}

The interfaces module also includes support for the other standard interfaces, such as private endpoints and customer managed keys.

Helping migration with moved()

Since we’re moving resources from AzureRM to AzAPI, we also need our good friend, the moved block, to avoid resources being destroyed and re-created.

1
2
3
4
5
6
7
8
9
moved {
  from = azurerm_role_assignment.vnet_level
  to   = azapi_resource.role_assignment
}

moved {
  from = azurerm_monitor_diagnostic_setting.this
  to   = azapi_resource.diagnostic_setting
}

If you’re not aware of this trick, check out one of my (surprisingly popular!) posts on moving resources from AzureRM to AzAPI from a few months back.

Interested in the end result?

If you’d like to see the result, I have this as an experimental PR on the virtual network module.

It is worth pointing out that just because a module is written in AzAPI, it doesn’t preclude the calling code from being based in AzureRM. Most of the examples that test the module use simple resources defined this way.

To put the cherry on the cake, I also wrote a pure AzAPI example to illustrate how this could be called with related resources all using AzAPI.

Since this is on a feature branch - some of the above code links may change, if you’re coming to this later - please drop me a note in the comments and I’ll update them if I can!

Call to action

If you’re looking at converting modules to AzAPI, or using AzAPI for an AVM module, please take advantage of the new utility module to standardise & simplify how to make the shared interfaces!

This post is licensed under CC BY 4.0 by the author.