Microsoft shifted the Azure Purview to be Tenant aligned, but the AzureRM Provider hasn’t made that move yet, so if you are deploying your Azure infrastructure using Terraform you need to do some alternative changes to get it to work, especially when you’re trying to deploy Purview securely using private networking.
After a few painful attempts I reverse engineered the ARM Templates to determine the correct AzAPI resources you need to create a working example of the new Azure Purview.
The problem with AzureRM and Purview
At the time of writing, the azurerm_purview_account resource is based on an older API version. In practice, that means:
- Deploys as multiple accounts.
- Missing or incomplete network configuration options.
- Private access behaving inconsistently.
- Limited control when locking down ingress and egress.
If you’re building using the new view of Purview these will not work, so instead we can use the latest API version 2024-04-01. Below is a working example with a Resource Group for the account and the Purview Account. You can find the documentation of the API using AzAPI on Microsoft.Purview/accounts – Microsoft Learn. You will notice it has been configured to all be private networked, which I will talk about in the later section.
resource "azurerm_resource_group" "purview" {
name = "rg-example-purview"
location = "uksouth"
tags = var.tags
}
resource "azapi_resource" "purview" {
type = "Microsoft.Purview/accounts@2024-04-01-preview"
name = "purview-main"
parent_id = azurerm_resource_group.purview.id
identity {
type = "SystemAssigned"
identity_ids = []
}
location = azurerm_resource_group.purview.location
tags = var.tags
body = {
properties = {
cloudConnectors = {
}
ingestionStorage = {
publicNetworkAccess = "Disabled"
}
managedEventHubState = "Disabled"
managedResourceGroupName = "${azurerm_resource_group.purview.name}-mgmt"
managedResourcesPublicNetworkAccess = "NotSpecified"
mergeInfo = {
}
publicNetworkAccess = "Disabled"
tenantEndpointState = "Enabled"
}
sku = {
capacity = 1
name = "Standard"
}
}
}
The networking issue nobody tells you about
When you deploy a Purview account, Azure quietly creates a managed storage account behind the scenes in a unmanaged subscription which is used for ingestion.
If you’re running a locked‑down environment (no public network access), Purview expects private connectivity to that storage account as well.
So you do what you’d normally do:
- Create private endpoints
- Disable public access
- Keep everything inside your virtual network
Sounds straightforward, but you don’t own this storage account and have no permissions over it. Therefore you can’t just deploy private endpoints, atleast not using the AzureRM provider.
If you try to create a Private Endpoint for the managed storage account using azurerm_private_endpoint, Terraform will fail every time. The AzureRM azurerm_private_endpoint always tries to auto‑approve the Private Endpoint connection.
That’s fine when:
- You own the target resource
- You control both ends of the connection
The fix: manual Private Link connections
The solution is not to fight Terraform, but to stop it auto‑approving in the first place. Instead, the Private Endpoint must be created in a pending state, so it can be approved later from within Purview itself. That’s done using:
manualPrivateLinkServiceConnections
And once again, this is where AzAPI is essential as AzureRM doesn’t expose this property. Using the Microsoft.Network/privateEndpoints in the AzAPI we can create the endpoints for both the blob and queue in your own virtual network against the storage account outputted by the Purview AzAPI call. You need to use the output as the Storage Account name is dynamically created, then post creation we can use the AzAPI to approve them.
Approving them is not the same as approving normal Private Endpoints as we don’t have the access, so instead we use the Purview Account API to trigger the action.
resource "azapi_resource" "purview_managed_private_endpoints" {
for_each = { for pep_type in ["blob", "queue"] : pep_type => pep_type }
type = "Microsoft.Network/privateEndpoints@2025-03-01"
name = "pep-${each.value}-${azapi_resource.purview.name}"
location = azurerm_resource_group.purview.location
parent_id = azurerm_resource_group.purview.id
tags = local.tags
body = {
properties = {
subnet = {
id = module.subnets["pe"].subnet_ids
}
manualPrivateLinkServiceConnections = [
{
name = "pep-${azapi_resource.purview.name}-${each.value}"
properties = {
privateLinkServiceId = azapi_resource.purview.output.properties.ingestionStorage.id
groupIds = [each.value]
}
}
]
}
}
depends_on = [azapi_resource.purview]
}
resource "azapi_resource_action" "approve_ingestion_private_endpoint" {
for_each = azapi_resource.purview_managed_private_endpoints
type = "Microsoft.Purview/accounts@2024-04-01-preview"
resource_id = azapi_resource.purview.id
# REST action name from the API spec
action = "ingestionPrivateEndpointConnectionStatus"
method = "POST"
body = {
privateEndpointId = each.value.id
status = "Approved"
}
response_export_values = ["*"]
}
However, don’t forget to still deploy your private endpoints for the account to be accessible itself. For this one you can still use the AzureRM if you would like as you own all these resources.
resource "azurerm_private_endpoint" "purview" {
for_each = { for pep_type in ["account", "platform", "portal"] : pep_type => pep_type }
name = "pep-${each.value}-${azapi_resource.purview.name}"
resource_group_name = azurerm_resource_group.purview.name
location = azurerm_resource_group.purview.location
subnet_id = module.subnets["pe"].subnet_ids
private_service_connection {
name = "psc-${each.value}-${azapi_resource.purview.name}"
is_manual_connection = false
private_connection_resource_id = azapi_resource.purview.id
private_connection_resource_alias = null
subresource_names = [each.value]
request_message = null
}
tags = local.tags
lifecycle {
ignore_changes = [tags["Deployment-date"], private_dns_zone_group]
}
depends_on = [azapi_resource.purview]
}