Data sources and outputs

Learn how to use the azapi_resource data source and the response_export_values to access additional information.

Introduction

Date Description
2021-04-29 Azure Web PubSub in Public Preview
2021-08-02 Developers start testing the preview functionality
2021-11-16 Azure Web PubSub goes GA
2021-12-06 Developers ask to include Web PubSub in the Terraform config
2022-01-28 azurerm v2.94: new resource azurerm_web_pubsub
2022-02-07 Switch to native support for the resource
2022-02-14 Asked to add system assigned managed identity

The developers are happy that you have managed identity working on the Web Pub Sub service, but they still need to systematically learn the tenant id and the object id for the identity.

We will do this in three ways:

  1. We’ll first look at the azapi_resource data source to see how it is used and to take advantage of its inbuilt support for an identity block. Great for this example (and we’ll show that in example1), but you may need to access other properties other than identity.
  2. So we will then switch to using response_export_values argument in the data source. The response_export_values is used across the azapi resources and data sources. The full JSON export will be collected and then in the example2 output block we will construct the desired object.
  3. We’ll remove the data source altogether and use response_export_values within the azapi_update_resource instead. In this one we will filter the response_export_values more to get the exact output we want to see for a simpler output block for example3.

Feel free to skip straight to example3 if this feels like going the long way round!

Starting configuration

Your main.tf file should be similar to this:

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "=2.94"
    }

    azapi = {
      source  = "azure/azapi"
      version = "=0.3.0"
    }
  }
}

provider "azurerm" {
  features {}
}

provider "azapi" {}

resource "azurerm_resource_group" "azapi_labs" {
  name     = "azapi_labs"
  location = "West Europe"
}

resource "azurerm_web_pubsub" "webpubsub" {
  name                = "azapi-labs-richeney"
  resource_group_name = azurerm_resource_group.azapi_labs.name
  location            = azurerm_resource_group.azapi_labs.location
  sku                 = "Free_F1"
  capacity            = 1
}

resource "azapi_update_resource" "webpubsub_identity" {
  type      = "Microsoft.SignalRService/WebPubSub@2021-10-01"
  name      = azurerm_web_pubsub.webpubsub.name
  parent_id = azurerm_resource_group.azapi_labs.id

  body = jsonencode({
    identity = {
      "type" : "SystemAssigned"
    }
  })
}

⚠️ You should have a different value for your azurerm_web_pubsub.webpubsub.name.

azapi_resource data source

The azapi_resource data source is useful to get core information about existing resources.

The data source is a match for a straight Get call. As an example Azure CLI az rest command where get is the default method:

az rest --uri "https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/azapi_labs/providers/Microsoft.SignalRService/webPubSub/azapi-labs-richeney?api-version=2021-10-01"

Let’s see how that example REST API transfers to an azapi_resource data source.

  1. Open the azapi_resource data source documentation

    Open the azapi_resource data source page.

    Note that an identity block is an exported attribute.

Example 1

  1. Add an azapi_resource data block to the main.tf

    data "azapi_resource" "webpubsub_identity" {
      type      = "Microsoft.SignalRService/WebPubSub@2021-10-01"
      name      = azurerm_web_pubsub.webpubsub.name
      parent_id = azurerm_resource_group.azapi_labs.id
    
      depends_on = [
        azapi_update_resource.webpubsub_identity
      ]
    }
    

    Note that we have an explicit dependency on the azapi_update_resource.

  2. Add an output

    output "example1" {
      value = data.azapi_resource.webpubsub_identity
    }
    
  3. Save main.tf

  4. Apply

    terraform apply
    
  5. View the output

    terraform output example1
    

    Example output:

    {
      "id" = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/azapi_labs/providers/Microsoft.SignalRService/WebPubSub/azapi-labs-richeney"
      "identity" = tolist([
        {
          "identity_ids" = tolist([])
          "principal_id" = "280ea032-f2cd-46cc-b66c-2234d079a88b"
          "tenant_id" = "72f988bf-86f1-41af-91ab-2d7cd011eb47"
          "type" = "SystemAssigned"
        },
      ])
      "location" = "westeurope"
      "name" = "azapi-labs-richeney"
      "output" = "{}"
      "parent_id" = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/azapi_labs"
      "response_export_values" = tolist(null) /* of string */
      "tags" = tomap({})
      "timeouts" = null /* object */
      "type" = "Microsoft.SignalRService/WebPubSub@2021-10-01"
    }
    

    Key points:

    • You could modify the output to get what the dev need, e.g.

      output "example1" {
        value = {
          tenant_id = data.azapi_resource.webpubsub_identity.identity[0].tenant_id
          object_id = data.azapi_resource.webpubsub_identity.identity[0].principal_id
        }
      }
      

      Good to know, but from a learning perspective this feels like cheating.

    • The output attribute is an empty JSON object {}

    • The response_export_values argument wants list(string), but is currently set to null

  6. Delete the output block (output "example1") from main.tf

Example 2

Let’s use response_export_values properly.

  1. Add response_export_values to the data source

    Think of response_export_values as a filter selecting the output JSON for that output attribute.

    If you want to see everything that is available then set response_export_values = ["*"].

    Update your data source to match the block below.

    data "azapi_resource" "webpubsub_identity" {
      type      = "Microsoft.SignalRService/WebPubSub@2021-10-01"
      name      = azurerm_web_pubsub.webpubsub.name
      parent_id = azurerm_resource_group.azapi_labs.id
    
      depends_on = [
        azapi_update_resource.webpubsub_identity
      ]
    
      response_export_values = ["*"]
    }
    
  2. Add an initial output

    output "example2" {
      value = jsondecode(data.azapi_resource.webpubsub_identity.output)
    }
    
  3. Apply

    terraform apply
    
  4. View the output

    terraform output example2
    

    Example output:

    {
      "id" = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/azapi_labs/providers/Microsoft.SignalRService/WebPubSub/azapi-labs-richeney"
      "identity" = {
        "principalId" = "280ea032-f2cd-46cc-b66c-2234d079a88b"
        "tenantId" = "72f988bf-86f1-41af-91ab-2d7cd011eb47"
        "type" = "SystemAssigned"
      }
      "location" = "westeurope"
      "name" = "azapi-labs-richeney"
      "properties" = {
        "disableAadAuth" = false
        "disableLocalAuth" = false
        "externalIP" = "20.61.103.175"
        "hostName" = "azapi-labs-richeney.webpubsub.azure.com"
        "hostNamePrefix" = "azapi-labs-richeney"
        "liveTraceConfiguration" = null
        "networkACLs" = {
          "defaultAction" = "Deny"
          "privateEndpoints" = []
          "publicNetwork" = {
            "allow" = [
              "ServerConnection",
              "ClientConnection",
              "RESTAPI",
              "Trace",
            ]
            "deny" = null
          }
        }
        "privateEndpointConnections" = []
        "provisioningState" = "Succeeded"
        "publicNetworkAccess" = "Enabled"
        "publicPort" = 443
        "resourceLogConfiguration" = null
        "serverPort" = 443
        "sharedPrivateLinkResources" = []
        "tls" = {
          "clientCertEnabled" = false
        }
        "version" = "1.0"
      }
      "sku" = {
        "capacity" = 1
        "name" = "Free_F1"
        "size" = "F1"
        "tier" = "Free"
      }
      "systemData" = {
        "createdAt" = "2022-05-13T14:53:39.8175203Z"
        "createdBy" = "richeney@microsoft.com"
        "createdByType" = "User"
        "lastModifiedAt" = "2022-05-30T13:58:25.7383904Z"
        "lastModifiedBy" = "richeney@microsoft.com"
        "lastModifiedByType" = "User"
      }
      "tags" = null
      "type" = "Microsoft.SignalRService/WebPubSub"
    }
    

    OK, so we have the full JSON output which is powerful.

    Note that the output attribute in the data source also includes sections we might now want such as systemData etc.

  5. Update the output block

    The example below will construct the object to match the specific requirement.

    output "example2" {
      value = {
        tenant_id = jsondecode(data.azapi_resource.webpubsub_identity.output).identity.tenantId
        object_id = jsondecode(data.azapi_resource.webpubsub_identity.output).identity.principalId
      }
    }
    

    Example output:

    example2 = {
      "object_id" = "280ea032-f2cd-46cc-b66c-2234d079a88b"
      "tenant_id" = "72f988bf-86f1-41af-91ab-2d7cd011eb47"
    }
    
  6. Delete the data source block (data "azapi_resource" "webpubsub_identity")

  7. Delete the output (output "example2")

Example 3

  1. Update the azapi_update_resource block

    Add in a more specific response_export_values filter to match the update.

    resource "azapi_update_resource" "webpubsub_identity" {
      type      = "Microsoft.SignalRService/WebPubSub@2021-10-01"
      name      = azurerm_web_pubsub.webpubsub.name
      parent_id = azurerm_resource_group.azapi_labs.id
    
      body = jsonencode({
        identity = {
          "type" : "SystemAssigned"
        }
      })
    
      response_export_values = [
        "identity.principalId",
        "identity.tenantId"
      ]
    }
    
  2. Add in an example3 output

    This is a simple version pulling out the entire identity JSON block, keeping the original keys.

    output "example3" {
      value = jsondecode(azapi_update_resource.webpubsub_identity.output).identity
    }
    

    Example output:

    example3 = {
      "principalId" = "280ea032-f2cd-46cc-b66c-2234d079a88b"
      "tenantId" = "72f988bf-86f1-41af-91ab-2d7cd011eb47"
    }
    
  3. Update to final output

    Let’s be more precise and construct the JSON block to specify keys in line with Terraform standards. We’ll rename it at the same time

    output "webpubsub_identity" {
      value = {
        tenant_id = jsondecode(azapi_update_resource.webpubsub_identity.output).identity.tenantId
        object_id = jsondecode(azapi_update_resource.webpubsub_identity.output).identity.principalId
      }
    }
    

    Example output:

    webpubsub_identity = {
      "object_id" = "280ea032-f2cd-46cc-b66c-2234d079a88b"
      "tenant_id" = "72f988bf-86f1-41af-91ab-2d7cd011eb47"
    }
    

    Perfect.

Summary

A few different approaches to the same (or similar) end result, but it allowed us to explore the data source and the response_export_values.

We used it to create Terraform outputs, but it could just as easily have been as an argument value for another resource type, e.g. an azurerm_role_assignment:

resource "azurerm_role_assignment" "example" {
  scope                = azurerm_resource_group.azapi_labs.id
  role_definition_name = "Reader"
  principal_id         = jsondecode(azapi_update_resource.webpubsub_identity.output).identity.principalId
}

Move on to the last lab and we’ll cleanly move to a fully native azurerm config.


Help us improve

Azure Citadel is a community site built on GitHub, please contribute and send a pull request

 Make a change